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/stockitem.h"
44 #include "wx/tokenzr.h"
45 #include "wx/msw/private.h"
47 // ----------------------------------------------------------------------------
49 // ----------------------------------------------------------------------------
51 #if wxUSE_EXTENDED_RTTI
53 WX_DEFINE_FLAGS( wxButtonStyle
)
55 wxBEGIN_FLAGS( wxButtonStyle
)
56 // new style border flags, we put them first to
57 // use them for streaming out
58 wxFLAGS_MEMBER(wxBORDER_SIMPLE
)
59 wxFLAGS_MEMBER(wxBORDER_SUNKEN
)
60 wxFLAGS_MEMBER(wxBORDER_DOUBLE
)
61 wxFLAGS_MEMBER(wxBORDER_RAISED
)
62 wxFLAGS_MEMBER(wxBORDER_STATIC
)
63 wxFLAGS_MEMBER(wxBORDER_NONE
)
65 // old style border flags
66 wxFLAGS_MEMBER(wxSIMPLE_BORDER
)
67 wxFLAGS_MEMBER(wxSUNKEN_BORDER
)
68 wxFLAGS_MEMBER(wxDOUBLE_BORDER
)
69 wxFLAGS_MEMBER(wxRAISED_BORDER
)
70 wxFLAGS_MEMBER(wxSTATIC_BORDER
)
71 wxFLAGS_MEMBER(wxBORDER
)
73 // standard window styles
74 wxFLAGS_MEMBER(wxTAB_TRAVERSAL
)
75 wxFLAGS_MEMBER(wxCLIP_CHILDREN
)
76 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW
)
77 wxFLAGS_MEMBER(wxWANTS_CHARS
)
78 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE
)
79 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB
)
80 wxFLAGS_MEMBER(wxVSCROLL
)
81 wxFLAGS_MEMBER(wxHSCROLL
)
83 wxFLAGS_MEMBER(wxBU_LEFT
)
84 wxFLAGS_MEMBER(wxBU_RIGHT
)
85 wxFLAGS_MEMBER(wxBU_TOP
)
86 wxFLAGS_MEMBER(wxBU_BOTTOM
)
87 wxFLAGS_MEMBER(wxBU_EXACTFIT
)
88 wxEND_FLAGS( wxButtonStyle
)
90 IMPLEMENT_DYNAMIC_CLASS_XTI(wxButton
, wxControl
,"wx/button.h")
92 wxBEGIN_PROPERTIES_TABLE(wxButton
)
93 wxEVENT_PROPERTY( Click
, wxEVT_COMMAND_BUTTON_CLICKED
, wxCommandEvent
)
95 wxPROPERTY( Font
, wxFont
, SetFont
, GetFont
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
96 wxPROPERTY( Label
, wxString
, SetLabel
, GetLabel
, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
98 wxPROPERTY_FLAGS( WindowStyle
, wxButtonStyle
, long , SetWindowStyleFlag
, GetWindowStyleFlag
, EMPTY_MACROVALUE
, 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
100 wxEND_PROPERTIES_TABLE()
102 wxBEGIN_HANDLERS_TABLE(wxButton
)
103 wxEND_HANDLERS_TABLE()
105 wxCONSTRUCTOR_6( wxButton
, wxWindow
* , Parent
, wxWindowID
, Id
, wxString
, Label
, wxPoint
, Position
, wxSize
, Size
, long , WindowStyle
)
109 IMPLEMENT_DYNAMIC_CLASS(wxButton
, wxControl
)
112 // this macro tries to adjust the default button height to a reasonable value
113 // using the char height as the base
114 #define BUTTON_HEIGHT_FROM_CHAR_HEIGHT(cy) (11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy)/10)
116 // ============================================================================
118 // ============================================================================
120 // ----------------------------------------------------------------------------
121 // creation/destruction
122 // ----------------------------------------------------------------------------
124 bool wxButton::Create(wxWindow
*parent
,
130 const wxValidator
& validator
,
131 const wxString
& name
)
134 if (label
.empty() && wxIsStockID(id
))
135 label
= wxGetStockLabel(id
);
137 if ( !CreateControl(parent
, id
, pos
, size
, style
, validator
, name
) )
141 WXDWORD msStyle
= MSWGetStyle(style
, &exstyle
);
144 // if the label contains several lines we must explicitly tell the button
145 // about it or it wouldn't draw it correctly ("\n"s would just appear as
148 // NB: we do it here and not in MSWGetStyle() because we need the label
149 // value and m_label is not set yet when MSWGetStyle() is called;
150 // besides changing BS_MULTILINE during run-time is pointless anyhow
151 if ( label
.find(_T('\n')) != wxString::npos
)
153 msStyle
|= BS_MULTILINE
;
157 return MSWCreateControl(_T("BUTTON"), msStyle
, pos
, size
, label
, exstyle
);
160 wxButton::~wxButton()
164 // ----------------------------------------------------------------------------
166 // ----------------------------------------------------------------------------
168 WXDWORD
wxButton::MSWGetStyle(long style
, WXDWORD
*exstyle
) const
170 // buttons never have an external border, they draw their own one
171 WXDWORD msStyle
= wxControl::MSWGetStyle
173 (style
& ~wxBORDER_MASK
) | wxBORDER_NONE
, exstyle
176 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
177 // each other in any resizeable dialog which has more than one button in
179 msStyle
|= WS_CLIPSIBLINGS
;
182 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
183 // and wxBU_RIGHT to get BS_CENTER!
184 if ( style
& wxBU_LEFT
)
186 if ( style
& wxBU_RIGHT
)
188 if ( style
& wxBU_TOP
)
190 if ( style
& wxBU_BOTTOM
)
191 msStyle
|= BS_BOTTOM
;
194 if ( style
& wxNO_BORDER
)
196 #endif // __WXWINCE__
202 // ----------------------------------------------------------------------------
203 // size management including autosizing
204 // ----------------------------------------------------------------------------
206 wxSize
wxButton::DoGetBestSize() const
209 int wChar
, hChar
, hBtn
;
210 wxGetCharSize(GetHWND(), &wChar
, &hChar
, GetFont());
212 wxString label
= wxGetWindowText(GetHWND());
213 if ( label
.find(_T('\n')) != wxString::npos
)
215 wxStringTokenizer
tokens( label
, wxT("\n") );
217 // the button height is proportional to the height of the font used
218 hBtn
= BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hChar
);
219 hBtn
+= hChar
*(tokens
.CountTokens()-1);
221 while (tokens
.HasMoreTokens())
223 wxString sub
= tokens
.GetNextToken();
225 GetTextExtent( sub
, &w
, NULL
);
232 GetTextExtent( label
, &wBtn
, NULL
);
234 // the button height is proportional to the height of the font used
235 hBtn
= BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hChar
);
238 // add a margin -- the button is wider than just its label
241 // all buttons have at least the standard size unless the user explicitly
242 // wants them to be of smaller size and used wxBU_EXACTFIT style when
243 // creating the button
244 if ( !HasFlag(wxBU_EXACTFIT
) )
246 wxSize sz
= GetDefaultSize();
255 return wxSize(wBtn
, hBtn
);
259 wxSize
wxButtonBase::GetDefaultSize()
261 static wxSize s_sizeBtn
;
263 if ( s_sizeBtn
.x
== 0 )
266 dc
.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT
));
268 // the size of a standard button in the dialog units is 50x14,
269 // translate this to pixels
270 // NB1: the multipliers come from the Windows convention
271 // NB2: the extra +1/+2 were needed to get the size be the same as the
272 // size of the buttons in the standard dialog - I don't know how
273 // this happens, but on my system this size is 75x23 in pixels and
274 // 23*8 isn't even divisible by 14... Would be nice to understand
275 // why these constants are needed though!
276 s_sizeBtn
.x
= (50 * (dc
.GetCharWidth() + 1))/4;
277 s_sizeBtn
.y
= ((14 * dc
.GetCharHeight()) + 2)/8;
283 // ----------------------------------------------------------------------------
284 // default button handling
285 // ----------------------------------------------------------------------------
288 "Everything you ever wanted to know about the default buttons" or "Why do we
289 have to do all this?"
291 In MSW the default button should be activated when the user presses Enter
292 and the current control doesn't process Enter itself somehow. This is
293 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
294 Another aspect of "defaultness" is that the default button has different
295 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
296 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
297 be unset if our parent window is not active so it should be unset whenever
298 we lose activation and set back when we regain it.
300 Final complication is that when a button is active, it should be the default
301 one, i.e. pressing Enter on a button always activates it and not another
304 We handle this by maintaining a permanent and a temporary default items in
305 wxControlContainer (both may be NULL). When a button becomes the current
306 control (i.e. gets focus) it sets itself as the temporary default which
307 ensures that it has the right appearance and that Enter will be redirected
308 to it. When the button loses focus, it unsets the temporary default and so
309 the default item will be the permanent default -- that is the default button
310 if any had been set or none otherwise, which is just what we want.
312 NB: all this is quite complicated by now and the worst is that normally
313 it shouldn't be necessary at all as for the normal Windows programs
314 DefWindowProc() and IsDialogMessage() take care of all this
315 automatically -- however in wxWidgets programs this doesn't work for
316 nested hierarchies (i.e. a notebook inside a notebook) for unknown
317 reason and so we have to reproduce all this code ourselves. It would be
318 very nice if we could avoid doing it.
321 // set this button as the (permanently) default one in its panel
322 void wxButton::SetDefault()
324 wxWindow
*parent
= GetParent();
326 wxCHECK_RET( parent
, _T("button without parent?") );
328 // set this one as the default button both for wxWidgets ...
329 wxWindow
*winOldDefault
= parent
->SetDefaultItem(this);
332 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
333 SetDefaultStyle(this, true);
336 // set this button as being currently default
337 void wxButton::SetTmpDefault()
339 wxWindow
*parent
= GetParent();
341 wxCHECK_RET( parent
, _T("button without parent?") );
343 wxWindow
*winOldDefault
= parent
->GetDefaultItem();
344 parent
->SetTmpDefaultItem(this);
346 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), false);
347 SetDefaultStyle(this, true);
350 // unset this button as currently default, it may still stay permanent default
351 void wxButton::UnsetTmpDefault()
353 wxWindow
*parent
= GetParent();
355 wxCHECK_RET( parent
, _T("button without parent?") );
357 parent
->SetTmpDefaultItem(NULL
);
359 wxWindow
*winOldDefault
= parent
->GetDefaultItem();
361 SetDefaultStyle(this, false);
362 SetDefaultStyle(wxDynamicCast(winOldDefault
, wxButton
), true);
367 wxButton::SetDefaultStyle(wxButton
*btn
, bool on
)
369 // we may be called with NULL pointer -- simpler to do the check here than
370 // in the caller which does wxDynamicCast()
374 // first, let DefDlgProc() know about the new default button
377 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
378 // focus at all any more
379 if ( !wxTheApp
->IsActive() )
382 // look for a panel-like window
383 wxWindow
*win
= btn
->GetParent();
384 while ( win
&& !win
->HasFlag(wxTAB_TRAVERSAL
) )
385 win
= win
->GetParent();
389 ::SendMessage(GetHwndOf(win
), DM_SETDEFID
, btn
->GetId(), 0L);
391 // sending DM_SETDEFID also changes the button style to
392 // BS_DEFPUSHBUTTON so there is nothing more to do
396 // then also change the style as needed
397 long style
= ::GetWindowLong(GetHwndOf(btn
), GWL_STYLE
);
398 if ( !(style
& BS_DEFPUSHBUTTON
) == on
)
400 // don't do it with the owner drawn buttons because it will
401 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
402 // BS_DEFPUSHBUTTON != 0)!
403 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
405 ::SendMessage(GetHwndOf(btn
), BM_SETSTYLE
,
406 on
? style
| BS_DEFPUSHBUTTON
407 : style
& ~BS_DEFPUSHBUTTON
,
412 // redraw the button - it will notice itself that it's
413 // [not] the default one [any longer]
417 //else: already has correct style
420 // ----------------------------------------------------------------------------
422 // ----------------------------------------------------------------------------
424 bool wxButton::SendClickEvent()
426 wxCommandEvent
event(wxEVT_COMMAND_BUTTON_CLICKED
, GetId());
427 event
.SetEventObject(this);
429 return ProcessCommand(event
);
432 void wxButton::Command(wxCommandEvent
& event
)
434 ProcessCommand(event
);
437 // ----------------------------------------------------------------------------
438 // event/message handlers
439 // ----------------------------------------------------------------------------
441 bool wxButton::MSWCommand(WXUINT param
, WXWORD
WXUNUSED(id
))
443 bool processed
= false;
446 // NOTE: Apparently older versions (NT 4?) of the common controls send
447 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
448 // buttons, so in order to send two EVT_BUTTON events we should
449 // catch both types. Currently (Feb 2003) up-to-date versions of
450 // win98, win2k and winXP all send two BN_CLICKED messages for
451 // all button types, so we don't catch BN_DOUBLECLICKED anymore
452 // in order to not get 3 EVT_BUTTON events. If this is a problem
453 // then we need to figure out which version of the comctl32 changed
454 // this behaviour and test for it.
456 case 1: // message came from an accelerator
457 case BN_CLICKED
: // normal buttons send this
458 processed
= SendClickEvent();
465 WXLRESULT
wxButton::MSWWindowProc(WXUINT nMsg
, WXWPARAM wParam
, WXLPARAM lParam
)
467 // when we receive focus, we want to temporarily become the default button in
468 // our parent panel so that pressing "Enter" would activate us -- and when
469 // losing it we should restore the previous default button as well
470 if ( nMsg
== WM_SETFOCUS
)
474 // let the default processing take place too
476 else if ( nMsg
== WM_KILLFOCUS
)
480 else if ( nMsg
== WM_LBUTTONDBLCLK
)
482 // emulate a click event to force an owner-drawn button to change its
483 // appearance - without this, it won't do it
484 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN
, wParam
, lParam
);
486 // and continue with processing the message normally as well
489 // let the base class do all real processing
490 return wxControl::MSWWindowProc(nMsg
, wParam
, lParam
);
493 // ----------------------------------------------------------------------------
494 // owner-drawn buttons support
495 // ----------------------------------------------------------------------------
501 static void DrawButtonText(HDC hdc
,
503 const wxString
& text
,
506 COLORREF colOld
= SetTextColor(hdc
, col
);
507 int modeOld
= SetBkMode(hdc
, TRANSPARENT
);
509 // Note: we must have DT_SINGLELINE for DT_VCENTER to work.
510 ::DrawText(hdc
, text
, text
.length(), pRect
, DT_SINGLELINE
| DT_CENTER
| DT_VCENTER
);
512 SetBkMode(hdc
, modeOld
);
513 SetTextColor(hdc
, colOld
);
516 static void DrawRect(HDC hdc
, const RECT
& r
)
518 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
519 wxDrawLine(hdc
, r
.right
, r
.top
, r
.right
, r
.bottom
);
520 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.left
, r
.bottom
);
521 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.left
, r
.top
);
524 void wxButton::MakeOwnerDrawn()
526 long style
= GetWindowLong(GetHwnd(), GWL_STYLE
);
527 if ( (style
& BS_OWNERDRAW
) != BS_OWNERDRAW
)
530 style
|= BS_OWNERDRAW
;
531 SetWindowLong(GetHwnd(), GWL_STYLE
, style
);
535 bool wxButton::SetBackgroundColour(const wxColour
&colour
)
537 if ( !wxControl::SetBackgroundColour(colour
) )
550 bool wxButton::SetForegroundColour(const wxColour
&colour
)
552 if ( !wxControl::SetForegroundColour(colour
) )
566 The button frame looks like this normally:
569 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
570 WH GB H = light grey (LIGHT)
571 WH GB G = dark grey (SHADOW)
572 WH GB B = black (DKSHADOW)
577 When the button is selected, the button becomes like this (the total button
578 size doesn't change):
589 When the button is pushed (while selected) it is like:
601 static void DrawButtonFrame(HDC hdc
, const RECT
& rectBtn
,
602 bool selected
, bool pushed
)
605 CopyRect(&r
, &rectBtn
);
607 HPEN hpenBlack
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DDKSHADOW
)),
608 hpenGrey
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DSHADOW
)),
609 hpenLightGr
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DLIGHT
)),
610 hpenWhite
= CreatePen(PS_SOLID
, 1, GetSysColor(COLOR_3DHILIGHT
));
612 HPEN hpenOld
= (HPEN
)SelectObject(hdc
, hpenBlack
);
621 (void)SelectObject(hdc
, hpenGrey
);
622 ::InflateRect(&r
, -1, -1);
632 ::InflateRect(&r
, -1, -1);
635 wxDrawLine(hdc
, r
.left
, r
.bottom
, r
.right
, r
.bottom
);
636 wxDrawLine(hdc
, r
.right
, r
.bottom
, r
.right
, r
.top
- 1);
638 (void)SelectObject(hdc
, hpenWhite
);
639 wxDrawLine(hdc
, r
.left
, r
.bottom
- 1, r
.left
, r
.top
);
640 wxDrawLine(hdc
, r
.left
, r
.top
, r
.right
, r
.top
);
642 (void)SelectObject(hdc
, hpenLightGr
);
643 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 2, r
.left
+ 1, r
.top
+ 1);
644 wxDrawLine(hdc
, r
.left
+ 1, r
.top
+ 1, r
.right
- 1, r
.top
+ 1);
646 (void)SelectObject(hdc
, hpenGrey
);
647 wxDrawLine(hdc
, r
.left
+ 1, r
.bottom
- 1, r
.right
- 1, r
.bottom
- 1);
648 wxDrawLine(hdc
, r
.right
- 1, r
.bottom
- 1, r
.right
- 1, r
.top
);
651 (void)SelectObject(hdc
, hpenOld
);
652 DeleteObject(hpenWhite
);
653 DeleteObject(hpenLightGr
);
654 DeleteObject(hpenGrey
);
655 DeleteObject(hpenBlack
);
658 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT
*wxdis
)
660 LPDRAWITEMSTRUCT lpDIS
= (LPDRAWITEMSTRUCT
)wxdis
;
663 CopyRect(&rectBtn
, &lpDIS
->rcItem
);
665 COLORREF colBg
= wxColourToRGB(GetBackgroundColour()),
666 colFg
= wxColourToRGB(GetForegroundColour());
668 HDC hdc
= lpDIS
->hDC
;
669 UINT state
= lpDIS
->itemState
;
671 // first, draw the background
672 HBRUSH hbrushBackground
= ::CreateSolidBrush(colBg
);
674 FillRect(hdc
, &rectBtn
, hbrushBackground
);
676 // draw the border for the current state
677 bool selected
= (state
& ODS_SELECTED
) != 0;
680 wxPanel
*panel
= wxDynamicCast(GetParent(), wxPanel
);
683 selected
= panel
->GetDefaultItem() == this;
686 bool pushed
= (SendMessage(GetHwnd(), BM_GETSTATE
, 0, 0) & BST_PUSHED
) != 0;
688 DrawButtonFrame(hdc
, rectBtn
, selected
, pushed
);
690 // draw the focus rect if needed
691 if ( state
& ODS_FOCUS
)
694 CopyRect(&rectFocus
, &rectBtn
);
696 // I don't know where does this constant come from, but this is how
697 // Windows draws them
698 InflateRect(&rectFocus
, -4, -4);
700 DrawFocusRect(hdc
, &rectFocus
);
705 // the label is shifted by 1 pixel to create "pushed" effect
706 OffsetRect(&rectBtn
, 1, 1);
709 DrawButtonText(hdc
, &rectBtn
, GetLabel(),
710 state
& ODS_DISABLED
? GetSysColor(COLOR_GRAYTEXT
)
713 ::DeleteObject(hbrushBackground
);
720 #endif // wxUSE_BUTTON