]> git.saurik.com Git - wxWidgets.git/blame - src/msw/button.cpp
don't declare inline function with dllexport declaration, this provokes mingw32 warni...
[wxWidgets.git] / src / msw / button.cpp
CommitLineData
2bda0e17 1/////////////////////////////////////////////////////////////////////////////
f1e01716 2// Name: src/msw/button.cpp
2bda0e17
KB
3// Purpose: wxButton
4// Author: Julian Smart
5// Modified by:
6// Created: 04/01/98
7// RCS-ID: $Id$
6c9a19aa 8// Copyright: (c) Julian Smart
65571936 9// Licence: wxWindows licence
2bda0e17
KB
10/////////////////////////////////////////////////////////////////////////////
11
edccf428
VZ
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
cd0b1709 19
2bda0e17
KB
20// For compilers that support precompilation, includes "wx.h".
21#include "wx/wxprec.h"
22
23#ifdef __BORLANDC__
edccf428 24 #pragma hdrstop
2bda0e17
KB
25#endif
26
1e6feb95
VZ
27#if wxUSE_BUTTON
28
f1e01716
WS
29#include "wx/button.h"
30
2bda0e17 31#ifndef WX_PRECOMP
bac409a0 32 #include "wx/app.h"
edccf428 33 #include "wx/brush.h"
4e938f5b 34 #include "wx/panel.h"
8a4df159 35 #include "wx/bmpbuttn.h"
fb39c7ec
RR
36 #include "wx/settings.h"
37 #include "wx/dcscreen.h"
61e6a2ab 38 #include "wx/dcclient.h"
b84aec03 39 #include "wx/toplevel.h"
2bda0e17
KB
40#endif
41
5f7bcb48 42#include "wx/stockitem.h"
2376eee2 43#include "wx/tokenzr.h"
2bda0e17
KB
44#include "wx/msw/private.h"
45
4e9da8b7
RD
46#if wxUSE_UXTHEME
47 #include "wx/msw/uxtheme.h"
48
49 // no need to include tmschema.h
50 #ifndef BP_PUSHBUTTON
51 #define BP_PUSHBUTTON 1
52
53 #define PBS_NORMAL 1
54 #define PBS_HOT 2
55 #define PBS_PRESSED 3
56 #define PBS_DISABLED 4
57 #define PBS_DEFAULTED 5
58
59 #define TMT_CONTENTMARGINS 3602
60 #endif
61#endif // wxUSE_UXTHEME
62
63#ifndef WM_THEMECHANGED
64 #define WM_THEMECHANGED 0x031A
65#endif
66
67#ifndef ODS_NOACCEL
68 #define ODS_NOACCEL 0x0100
69#endif
70
71#ifndef ODS_NOFOCUSRECT
72 #define ODS_NOFOCUSRECT 0x0200
73#endif
74
edccf428
VZ
75// ----------------------------------------------------------------------------
76// macros
77// ----------------------------------------------------------------------------
78
51596bcb 79#if wxUSE_EXTENDED_RTTI
067e9be6 80
f3291a82
SC
81WX_DEFINE_FLAGS( wxButtonStyle )
82
3ff066a4 83wxBEGIN_FLAGS( wxButtonStyle )
f3291a82
SC
84 // new style border flags, we put them first to
85 // use them for streaming out
3ff066a4
SC
86 wxFLAGS_MEMBER(wxBORDER_SIMPLE)
87 wxFLAGS_MEMBER(wxBORDER_SUNKEN)
88 wxFLAGS_MEMBER(wxBORDER_DOUBLE)
89 wxFLAGS_MEMBER(wxBORDER_RAISED)
90 wxFLAGS_MEMBER(wxBORDER_STATIC)
91 wxFLAGS_MEMBER(wxBORDER_NONE)
fcf90ee1 92
f3291a82 93 // old style border flags
3ff066a4
SC
94 wxFLAGS_MEMBER(wxSIMPLE_BORDER)
95 wxFLAGS_MEMBER(wxSUNKEN_BORDER)
96 wxFLAGS_MEMBER(wxDOUBLE_BORDER)
97 wxFLAGS_MEMBER(wxRAISED_BORDER)
98 wxFLAGS_MEMBER(wxSTATIC_BORDER)
cb0afb26 99 wxFLAGS_MEMBER(wxBORDER)
f3291a82 100
bc9fb572 101 // standard window styles
3ff066a4
SC
102 wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
103 wxFLAGS_MEMBER(wxCLIP_CHILDREN)
104 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
105 wxFLAGS_MEMBER(wxWANTS_CHARS)
cb0afb26 106 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
3ff066a4
SC
107 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
108 wxFLAGS_MEMBER(wxVSCROLL)
109 wxFLAGS_MEMBER(wxHSCROLL)
110
111 wxFLAGS_MEMBER(wxBU_LEFT)
112 wxFLAGS_MEMBER(wxBU_RIGHT)
113 wxFLAGS_MEMBER(wxBU_TOP)
114 wxFLAGS_MEMBER(wxBU_BOTTOM)
115 wxFLAGS_MEMBER(wxBU_EXACTFIT)
116wxEND_FLAGS( wxButtonStyle )
067e9be6 117
51596bcb
SC
118IMPLEMENT_DYNAMIC_CLASS_XTI(wxButton, wxControl,"wx/button.h")
119
3ff066a4 120wxBEGIN_PROPERTIES_TABLE(wxButton)
fcf90ee1 121 wxEVENT_PROPERTY( Click , wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEvent)
067e9be6 122
fcf90ee1
WS
123 wxPROPERTY( Font , wxFont , SetFont , GetFont , EMPTY_MACROVALUE, 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
124 wxPROPERTY( Label, wxString , SetLabel, GetLabel, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
067e9be6 125
af498247 126 wxPROPERTY_FLAGS( WindowStyle , wxButtonStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
51741307 127
3ff066a4 128wxEND_PROPERTIES_TABLE()
51596bcb 129
3ff066a4
SC
130wxBEGIN_HANDLERS_TABLE(wxButton)
131wxEND_HANDLERS_TABLE()
51596bcb 132
3ff066a4 133wxCONSTRUCTOR_6( wxButton , wxWindow* , Parent , wxWindowID , Id , wxString , Label , wxPoint , Position , wxSize , Size , long , WindowStyle )
51596bcb
SC
134
135
136#else
cd0b1709 137IMPLEMENT_DYNAMIC_CLASS(wxButton, wxControl)
51596bcb 138#endif
2bda0e17 139
edccf428
VZ
140// this macro tries to adjust the default button height to a reasonable value
141// using the char height as the base
1c4a764c 142#define BUTTON_HEIGHT_FROM_CHAR_HEIGHT(cy) (11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy)/10)
2bda0e17 143
edccf428
VZ
144// ============================================================================
145// implementation
146// ============================================================================
147
148// ----------------------------------------------------------------------------
149// creation/destruction
150// ----------------------------------------------------------------------------
151
152bool wxButton::Create(wxWindow *parent,
153 wxWindowID id,
5f7bcb48 154 const wxString& lbl,
edccf428
VZ
155 const wxPoint& pos,
156 const wxSize& size,
157 long style,
158 const wxValidator& validator,
159 const wxString& name)
2bda0e17 160{
5f7bcb48
VS
161 wxString label(lbl);
162 if (label.empty() && wxIsStockID(id))
c87fc285
JS
163 {
164 // On Windows, some buttons aren't supposed to have
165 // mnemonics, so strip them out.
f1e01716
WS
166
167 label = wxGetStockLabel(id
c87fc285
JS
168#if defined(__WXMSW__) || defined(__WXWINCE__)
169 , ( id != wxID_OK &&
170 id != wxID_CANCEL &&
171 id != wxID_CLOSE )
172#endif
173 );
f1e01716
WS
174 }
175
5b2f31eb 176 if ( !CreateControl(parent, id, pos, size, style, validator, name) )
fcf90ee1 177 return false;
edccf428 178
8292017c
VZ
179 WXDWORD exstyle;
180 WXDWORD msStyle = MSWGetStyle(style, &exstyle);
181
182#ifdef __WIN32__
183 // if the label contains several lines we must explicitly tell the button
184 // about it or it wouldn't draw it correctly ("\n"s would just appear as
185 // black boxes)
186 //
187 // NB: we do it here and not in MSWGetStyle() because we need the label
188 // value and m_label is not set yet when MSWGetStyle() is called;
189 // besides changing BS_MULTILINE during run-time is pointless anyhow
190 if ( label.find(_T('\n')) != wxString::npos )
191 {
192 msStyle |= BS_MULTILINE;
193 }
194#endif // __WIN32__
195
196 return MSWCreateControl(_T("BUTTON"), msStyle, pos, size, label, exstyle);
5b2f31eb
VZ
197}
198
199wxButton::~wxButton()
200{
6c20e8f8
VZ
201 wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
202 if ( tlw && tlw->GetTmpDefaultItem() == this )
789367e1
VZ
203 {
204 UnsetTmpDefault();
205 }
5b2f31eb 206}
edccf428 207
5b2f31eb
VZ
208// ----------------------------------------------------------------------------
209// flags
210// ----------------------------------------------------------------------------
cd0b1709 211
5b2f31eb
VZ
212WXDWORD wxButton::MSWGetStyle(long style, WXDWORD *exstyle) const
213{
214 // buttons never have an external border, they draw their own one
215 WXDWORD msStyle = wxControl::MSWGetStyle
216 (
217 (style & ~wxBORDER_MASK) | wxBORDER_NONE, exstyle
218 );
f6bcfd97 219
5b2f31eb
VZ
220 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
221 // each other in any resizeable dialog which has more than one button in
222 // the bottom
223 msStyle |= WS_CLIPSIBLINGS;
b0766406 224
f6bcfd97 225#ifdef __WIN32__
5b2f31eb
VZ
226 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
227 // and wxBU_RIGHT to get BS_CENTER!
228 if ( style & wxBU_LEFT )
f6bcfd97 229 msStyle |= BS_LEFT;
5b2f31eb 230 if ( style & wxBU_RIGHT )
f6bcfd97 231 msStyle |= BS_RIGHT;
5b2f31eb 232 if ( style & wxBU_TOP )
f6bcfd97 233 msStyle |= BS_TOP;
5b2f31eb 234 if ( style & wxBU_BOTTOM )
f6bcfd97 235 msStyle |= BS_BOTTOM;
8cc4850c 236#ifndef __WXWINCE__
8a094d7b
JS
237 // flat 2d buttons
238 if ( style & wxNO_BORDER )
239 msStyle |= BS_FLAT;
8cc4850c 240#endif // __WXWINCE__
5b2f31eb 241#endif // __WIN32__
edccf428 242
5b2f31eb 243 return msStyle;
2bda0e17
KB
244}
245
edccf428
VZ
246// ----------------------------------------------------------------------------
247// size management including autosizing
248// ----------------------------------------------------------------------------
249
f68586e5 250wxSize wxButton::DoGetBestSize() const
2bda0e17 251{
c4e28380 252 wxClientDC dc(wx_const_cast(wxButton *, this));
0c8b041f 253 dc.SetFont(GetFont());
4438caf4 254
c4e28380
VZ
255 wxCoord wBtn,
256 hBtn;
32cd189d 257 dc.GetMultiLineTextExtent(GetLabelText(), &wBtn, &hBtn);
2376eee2 258
1e023926 259 // add a margin -- the button is wider than just its label
c4e28380
VZ
260 wBtn += 3*GetCharWidth();
261 hBtn = BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hBtn);
4438caf4 262
1e023926
VZ
263 // all buttons have at least the standard size unless the user explicitly
264 // wants them to be of smaller size and used wxBU_EXACTFIT style when
265 // creating the button
266 if ( !HasFlag(wxBU_EXACTFIT) )
188b9f2e
RD
267 {
268 wxSize sz = GetDefaultSize();
1e023926
VZ
269 if (wBtn > sz.x)
270 sz.x = wBtn;
271 if (hBtn > sz.y)
272 sz.y = hBtn;
273
188b9f2e
RD
274 return sz;
275 }
a63f2bec 276
31582e4e
RD
277 wxSize best(wBtn, hBtn);
278 CacheBestSize(best);
279 return best;
2bda0e17
KB
280}
281
e1f36ff8 282/* static */
1e6feb95 283wxSize wxButtonBase::GetDefaultSize()
e1f36ff8 284{
8c3c31d4 285 static wxSize s_sizeBtn;
e1f36ff8 286
8c3c31d4
VZ
287 if ( s_sizeBtn.x == 0 )
288 {
289 wxScreenDC dc;
a756f210 290 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
8c3c31d4
VZ
291
292 // the size of a standard button in the dialog units is 50x14,
293 // translate this to pixels
294 // NB1: the multipliers come from the Windows convention
295 // NB2: the extra +1/+2 were needed to get the size be the same as the
296 // size of the buttons in the standard dialog - I don't know how
297 // this happens, but on my system this size is 75x23 in pixels and
298 // 23*8 isn't even divisible by 14... Would be nice to understand
299 // why these constants are needed though!
300 s_sizeBtn.x = (50 * (dc.GetCharWidth() + 1))/4;
301 s_sizeBtn.y = ((14 * dc.GetCharHeight()) + 2)/8;
302 }
e1f36ff8 303
8c3c31d4 304 return s_sizeBtn;
e1f36ff8
VZ
305}
306
4438caf4 307// ----------------------------------------------------------------------------
036da5e3 308// default button handling
4438caf4
VZ
309// ----------------------------------------------------------------------------
310
d78f09e2
VZ
311/*
312 "Everything you ever wanted to know about the default buttons" or "Why do we
313 have to do all this?"
314
315 In MSW the default button should be activated when the user presses Enter
316 and the current control doesn't process Enter itself somehow. This is
317 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
318 Another aspect of "defaultness" is that the default button has different
319 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
7fb1b2b4
VZ
320 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
321 be unset if our parent window is not active so it should be unset whenever
322 we lose activation and set back when we regain it.
d78f09e2
VZ
323
324 Final complication is that when a button is active, it should be the default
325 one, i.e. pressing Enter on a button always activates it and not another
326 one.
327
328 We handle this by maintaining a permanent and a temporary default items in
329 wxControlContainer (both may be NULL). When a button becomes the current
330 control (i.e. gets focus) it sets itself as the temporary default which
331 ensures that it has the right appearance and that Enter will be redirected
332 to it. When the button loses focus, it unsets the temporary default and so
333 the default item will be the permanent default -- that is the default button
334 if any had been set or none otherwise, which is just what we want.
335
7fb1b2b4
VZ
336 NB: all this is quite complicated by now and the worst is that normally
337 it shouldn't be necessary at all as for the normal Windows programs
338 DefWindowProc() and IsDialogMessage() take care of all this
77ffb593 339 automatically -- however in wxWidgets programs this doesn't work for
7fb1b2b4
VZ
340 nested hierarchies (i.e. a notebook inside a notebook) for unknown
341 reason and so we have to reproduce all this code ourselves. It would be
342 very nice if we could avoid doing it.
d78f09e2
VZ
343 */
344
036da5e3 345// set this button as the (permanently) default one in its panel
94aff5ff 346wxWindow *wxButton::SetDefault()
2bda0e17 347{
77ffb593 348 // set this one as the default button both for wxWidgets ...
94aff5ff 349 wxWindow *winOldDefault = wxButtonBase::SetDefault();
036da5e3 350
7fb1b2b4 351 // ... and Windows
fcf90ee1
WS
352 SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), false);
353 SetDefaultStyle(this, true);
ebc0b155
VZ
354
355 return winOldDefault;
036da5e3
VZ
356}
357
7fb1b2b4 358// set this button as being currently default
036da5e3
VZ
359void wxButton::SetTmpDefault()
360{
6c20e8f8 361 wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
036da5e3 362
6c20e8f8 363 wxCHECK_RET( tlw, _T("button without top level window?") );
036da5e3 364
6c20e8f8
VZ
365 wxWindow *winOldDefault = tlw->GetDefaultItem();
366 tlw->SetTmpDefaultItem(this);
7fb1b2b4 367
fcf90ee1
WS
368 SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), false);
369 SetDefaultStyle(this, true);
036da5e3
VZ
370}
371
7fb1b2b4 372// unset this button as currently default, it may still stay permanent default
036da5e3
VZ
373void wxButton::UnsetTmpDefault()
374{
6c20e8f8 375 wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
036da5e3 376
6c20e8f8 377 wxCHECK_RET( tlw, _T("button without top level window?") );
036da5e3 378
6c20e8f8 379 tlw->SetTmpDefaultItem(NULL);
036da5e3 380
6c20e8f8 381 wxWindow *winOldDefault = tlw->GetDefaultItem();
7fb1b2b4 382
fcf90ee1
WS
383 SetDefaultStyle(this, false);
384 SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), true);
036da5e3 385}
8ed57d93 386
036da5e3
VZ
387/* static */
388void
7fb1b2b4 389wxButton::SetDefaultStyle(wxButton *btn, bool on)
036da5e3 390{
7fb1b2b4
VZ
391 // we may be called with NULL pointer -- simpler to do the check here than
392 // in the caller which does wxDynamicCast()
393 if ( !btn )
394 return;
395
396 // first, let DefDlgProc() know about the new default button
397 if ( on )
5d1d2d46 398 {
7fb1b2b4
VZ
399 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
400 // focus at all any more
401 if ( !wxTheApp->IsActive() )
402 return;
cd0b1709 403
6c20e8f8
VZ
404 wxWindow * const tlw = wxGetTopLevelParent(btn);
405 wxCHECK_RET( tlw, _T("button without top level window?") );
cef55d64 406
6c20e8f8 407 ::SendMessage(GetHwndOf(tlw), DM_SETDEFID, btn->GetId(), 0L);
cef55d64
VZ
408
409 // sending DM_SETDEFID also changes the button style to
410 // BS_DEFPUSHBUTTON so there is nothing more to do
5d1d2d46
VZ
411 }
412
7fb1b2b4
VZ
413 // then also change the style as needed
414 long style = ::GetWindowLong(GetHwndOf(btn), GWL_STYLE);
415 if ( !(style & BS_DEFPUSHBUTTON) == on )
be4017f8 416 {
7fb1b2b4
VZ
417 // don't do it with the owner drawn buttons because it will
418 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
419 // BS_DEFPUSHBUTTON != 0)!
036da5e3
VZ
420 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
421 {
7fb1b2b4
VZ
422 ::SendMessage(GetHwndOf(btn), BM_SETSTYLE,
423 on ? style | BS_DEFPUSHBUTTON
424 : style & ~BS_DEFPUSHBUTTON,
425 1L /* redraw */);
036da5e3 426 }
7fb1b2b4 427 else // owner drawn
036da5e3 428 {
7fb1b2b4
VZ
429 // redraw the button - it will notice itself that it's
430 // [not] the default one [any longer]
431 btn->Refresh();
036da5e3 432 }
be4017f8 433 }
7fb1b2b4 434 //else: already has correct style
2bda0e17
KB
435}
436
edccf428
VZ
437// ----------------------------------------------------------------------------
438// helpers
439// ----------------------------------------------------------------------------
440
441bool wxButton::SendClickEvent()
2bda0e17 442{
edccf428
VZ
443 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
444 event.SetEventObject(this);
445
446 return ProcessCommand(event);
2bda0e17
KB
447}
448
edccf428 449void wxButton::Command(wxCommandEvent & event)
2bda0e17 450{
edccf428 451 ProcessCommand(event);
2bda0e17
KB
452}
453
edccf428
VZ
454// ----------------------------------------------------------------------------
455// event/message handlers
456// ----------------------------------------------------------------------------
2bda0e17 457
33ac7e6f 458bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
edccf428 459{
fcf90ee1 460 bool processed = false;
57c0af52 461 switch ( param )
edccf428 462 {
5aab763c
RD
463 // NOTE: Apparently older versions (NT 4?) of the common controls send
464 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
87b6002d 465 // buttons, so in order to send two EVT_BUTTON events we should
5aab763c
RD
466 // catch both types. Currently (Feb 2003) up-to-date versions of
467 // win98, win2k and winXP all send two BN_CLICKED messages for
468 // all button types, so we don't catch BN_DOUBLECLICKED anymore
469 // in order to not get 3 EVT_BUTTON events. If this is a problem
470 // then we need to figure out which version of the comctl32 changed
471 // this behaviour and test for it.
472
a95e38c0
VZ
473 case 1: // message came from an accelerator
474 case BN_CLICKED: // normal buttons send this
57c0af52
VZ
475 processed = SendClickEvent();
476 break;
edccf428 477 }
2bda0e17 478
edccf428 479 return processed;
2bda0e17
KB
480}
481
c140b7e7 482WXLRESULT wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
678cd6de 483{
87b6002d 484 // when we receive focus, we want to temporarily become the default button in
036da5e3
VZ
485 // our parent panel so that pressing "Enter" would activate us -- and when
486 // losing it we should restore the previous default button as well
be4017f8 487 if ( nMsg == WM_SETFOCUS )
678cd6de 488 {
036da5e3 489 SetTmpDefault();
be4017f8 490
036da5e3
VZ
491 // let the default processing take place too
492 }
493 else if ( nMsg == WM_KILLFOCUS )
494 {
495 UnsetTmpDefault();
678cd6de 496 }
a95e38c0
VZ
497 else if ( nMsg == WM_LBUTTONDBLCLK )
498 {
f6bcfd97
BP
499 // emulate a click event to force an owner-drawn button to change its
500 // appearance - without this, it won't do it
501 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN, wParam, lParam);
502
036da5e3 503 // and continue with processing the message normally as well
a95e38c0 504 }
4e9da8b7
RD
505#if wxUSE_UXTHEME
506 else if ( nMsg == WM_THEMECHANGED )
507 {
508 // need to recalculate the best size here
509 // as the theme size might have changed
510 InvalidateBestSize();
511 }
512 else if ( wxUxThemeEngine::GetIfActive() )
513 {
514 // we need to Refresh() if mouse has entered or left window
515 // so we can update the hot tracking state
516 // must use m_mouseInWindow here instead of IsMouseInWindow()
517 // since we need to know the first time the mouse enters the window
518 // and IsMouseInWindow() would return true in this case
519 if ( ( nMsg == WM_MOUSEMOVE && !m_mouseInWindow ) ||
520 nMsg == WM_MOUSELEAVE )
521 {
522 Refresh();
523 }
524 }
525#endif // wxUSE_UXTHEME
678cd6de
VZ
526
527 // let the base class do all real processing
528 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
529}
cd0b1709
VZ
530
531// ----------------------------------------------------------------------------
532// owner-drawn buttons support
533// ----------------------------------------------------------------------------
534
535#ifdef __WIN32__
536
537// drawing helpers
538
539static void DrawButtonText(HDC hdc,
540 RECT *pRect,
541 const wxString& text,
542 COLORREF col)
543{
544 COLORREF colOld = SetTextColor(hdc, col);
545 int modeOld = SetBkMode(hdc, TRANSPARENT);
546
cbbb6724
VZ
547 if ( text.find(_T('\n')) != wxString::npos )
548 {
549 // draw multiline label
550
551 // first we need to compute its bounding rect
552 RECT rc;
553 ::CopyRect(&rc, pRect);
554 ::DrawText(hdc, text, text.length(), &rc, DT_CENTER | DT_CALCRECT);
555
556 // now center this rect inside the entire button area
557 const LONG w = rc.right - rc.left;
558 const LONG h = rc.bottom - rc.top;
559 rc.left = (pRect->right - pRect->left)/2 - w/2;
560 rc.right = rc.left+w;
561 rc.top = (pRect->bottom - pRect->top)/2 - h/2;
562 rc.bottom = rc.top+h;
563
564 ::DrawText(hdc, text, text.length(), &rc, DT_CENTER);
565 }
566 else // single line label
567 {
568 // Note: we must have DT_SINGLELINE for DT_VCENTER to work.
569 ::DrawText(hdc, text, text.length(), pRect,
570 DT_SINGLELINE | DT_CENTER | DT_VCENTER);
571 }
cd0b1709
VZ
572
573 SetBkMode(hdc, modeOld);
574 SetTextColor(hdc, colOld);
575}
576
577static void DrawRect(HDC hdc, const RECT& r)
578{
4676948b
JS
579 wxDrawLine(hdc, r.left, r.top, r.right, r.top);
580 wxDrawLine(hdc, r.right, r.top, r.right, r.bottom);
581 wxDrawLine(hdc, r.right, r.bottom, r.left, r.bottom);
582 wxDrawLine(hdc, r.left, r.bottom, r.left, r.top);
cd0b1709
VZ
583}
584
585void wxButton::MakeOwnerDrawn()
586{
587 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
9750fc42 588 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
cd0b1709
VZ
589 {
590 // make it so
591 style |= BS_OWNERDRAW;
592 SetWindowLong(GetHwnd(), GWL_STYLE, style);
593 }
594}
595
596bool wxButton::SetBackgroundColour(const wxColour &colour)
597{
598 if ( !wxControl::SetBackgroundColour(colour) )
599 {
600 // nothing to do
fcf90ee1 601 return false;
cd0b1709
VZ
602 }
603
604 MakeOwnerDrawn();
605
606 Refresh();
607
fcf90ee1 608 return true;
cd0b1709
VZ
609}
610
611bool wxButton::SetForegroundColour(const wxColour &colour)
612{
613 if ( !wxControl::SetForegroundColour(colour) )
614 {
615 // nothing to do
fcf90ee1 616 return false;
cd0b1709
VZ
617 }
618
619 MakeOwnerDrawn();
620
621 Refresh();
622
fcf90ee1 623 return true;
cd0b1709
VZ
624}
625
626/*
627 The button frame looks like this normally:
628
629 WWWWWWWWWWWWWWWWWWB
16162a64
GRG
630 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
631 WH GB H = light grey (LIGHT)
632 WH GB G = dark grey (SHADOW)
633 WH GB B = black (DKSHADOW)
634 WH GB
cd0b1709
VZ
635 WGGGGGGGGGGGGGGGGGB
636 BBBBBBBBBBBBBBBBBBB
637
638 When the button is selected, the button becomes like this (the total button
639 size doesn't change):
640
641 BBBBBBBBBBBBBBBBBBB
642 BWWWWWWWWWWWWWWWWBB
16162a64
GRG
643 BWHHHHHHHHHHHHHHGBB
644 BWH GBB
645 BWH GBB
cd0b1709
VZ
646 BWGGGGGGGGGGGGGGGBB
647 BBBBBBBBBBBBBBBBBBB
648 BBBBBBBBBBBBBBBBBBB
649
650 When the button is pushed (while selected) it is like:
651
652 BBBBBBBBBBBBBBBBBBB
653 BGGGGGGGGGGGGGGGGGB
654 BG GB
655 BG GB
656 BG GB
16162a64 657 BG GB
cd0b1709
VZ
658 BGGGGGGGGGGGGGGGGGB
659 BBBBBBBBBBBBBBBBBBB
660*/
661
662static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
663 bool selected, bool pushed)
664{
665 RECT r;
666 CopyRect(&r, &rectBtn);
667
16162a64
GRG
668 HPEN hpenBlack = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW)),
669 hpenGrey = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW)),
670 hpenLightGr = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT)),
671 hpenWhite = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT));
cd0b1709
VZ
672
673 HPEN hpenOld = (HPEN)SelectObject(hdc, hpenBlack);
674
675 r.right--;
676 r.bottom--;
677
678 if ( pushed )
679 {
680 DrawRect(hdc, r);
681
682 (void)SelectObject(hdc, hpenGrey);
fcf90ee1 683 ::InflateRect(&r, -1, -1);
cd0b1709
VZ
684
685 DrawRect(hdc, r);
686 }
687 else // !pushed
688 {
689 if ( selected )
690 {
691 DrawRect(hdc, r);
692
fcf90ee1 693 ::InflateRect(&r, -1, -1);
cd0b1709
VZ
694 }
695
4676948b
JS
696 wxDrawLine(hdc, r.left, r.bottom, r.right, r.bottom);
697 wxDrawLine(hdc, r.right, r.bottom, r.right, r.top - 1);
cd0b1709
VZ
698
699 (void)SelectObject(hdc, hpenWhite);
4676948b
JS
700 wxDrawLine(hdc, r.left, r.bottom - 1, r.left, r.top);
701 wxDrawLine(hdc, r.left, r.top, r.right, r.top);
cd0b1709 702
16162a64 703 (void)SelectObject(hdc, hpenLightGr);
4676948b
JS
704 wxDrawLine(hdc, r.left + 1, r.bottom - 2, r.left + 1, r.top + 1);
705 wxDrawLine(hdc, r.left + 1, r.top + 1, r.right - 1, r.top + 1);
16162a64 706
cd0b1709 707 (void)SelectObject(hdc, hpenGrey);
4676948b
JS
708 wxDrawLine(hdc, r.left + 1, r.bottom - 1, r.right - 1, r.bottom - 1);
709 wxDrawLine(hdc, r.right - 1, r.bottom - 1, r.right - 1, r.top);
cd0b1709
VZ
710 }
711
712 (void)SelectObject(hdc, hpenOld);
713 DeleteObject(hpenWhite);
16162a64 714 DeleteObject(hpenLightGr);
cd0b1709
VZ
715 DeleteObject(hpenGrey);
716 DeleteObject(hpenBlack);
717}
718
4e9da8b7
RD
719#if wxUSE_UXTHEME
720static
721void MSWDrawXPBackground(wxButton *button, WXDRAWITEMSTRUCT *wxdis)
cd0b1709
VZ
722{
723 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
4e9da8b7
RD
724 HDC hdc = lpDIS->hDC;
725 UINT state = lpDIS->itemState;
cd0b1709
VZ
726 RECT rectBtn;
727 CopyRect(&rectBtn, &lpDIS->rcItem);
728
4e9da8b7
RD
729 wxUxThemeHandle theme(button, L"BUTTON");
730 int iState;
731
732 if ( state & ODS_SELECTED )
733 {
734 iState = PBS_PRESSED;
735 }
736 else if ( button->HasCapture() || button->IsMouseInWindow() )
737 {
738 iState = PBS_HOT;
739 }
740 else if ( state & ODS_FOCUS )
741 {
742 iState = PBS_DEFAULTED;
743 }
744 else if ( state & ODS_DISABLED )
745 {
746 iState = PBS_DISABLED;
747 }
748 else
749 {
750 iState = PBS_NORMAL;
751 }
cd0b1709 752
4e9da8b7
RD
753 // draw parent background if needed
754 if ( wxUxThemeEngine::Get()->IsThemeBackgroundPartiallyTransparent(theme,
755 BP_PUSHBUTTON,
756 iState) )
757 {
758 wxUxThemeEngine::Get()->DrawThemeParentBackground(GetHwndOf(button), hdc, &rectBtn);
759 }
760
761 // draw background
762 wxUxThemeEngine::Get()->DrawThemeBackground(theme, hdc, BP_PUSHBUTTON, iState,
763 &rectBtn, NULL);
764
765 // calculate content area margins
766 MARGINS margins;
767 wxUxThemeEngine::Get()->GetThemeMargins(theme, hdc, BP_PUSHBUTTON, iState,
768 TMT_CONTENTMARGINS, &rectBtn, &margins);
769 RECT rectClient;
770 ::CopyRect(&rectClient, &rectBtn);
771 ::InflateRect(&rectClient, -margins.cxLeftWidth, -margins.cyTopHeight);
772
773 // if focused and !nofocus rect
774 if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) )
775 {
776 DrawFocusRect(hdc, &rectClient);
777 }
778
779 if ( button->UseBgCol() )
780 {
781 COLORREF colBg = wxColourToRGB(button->GetBackgroundColour());
782 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
783
784 // don't overwrite the focus rect
785 ::InflateRect(&rectClient, -1, -1);
786 FillRect(hdc, &rectClient, hbrushBackground);
787 ::DeleteObject(hbrushBackground);
788 }
789}
790#endif // wxUSE_UXTHEME
791
792bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
793{
794 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
cd0b1709
VZ
795 HDC hdc = lpDIS->hDC;
796 UINT state = lpDIS->itemState;
4e9da8b7
RD
797 RECT rectBtn;
798 CopyRect(&rectBtn, &lpDIS->rcItem);
cd0b1709 799
4e9da8b7
RD
800#if wxUSE_UXTHEME
801 if ( wxUxThemeEngine::GetIfActive() )
802 {
803 MSWDrawXPBackground(this, wxdis);
804 }
805 else
806#endif // wxUSE_UXTHEME
807 {
808 COLORREF colBg = wxColourToRGB(GetBackgroundColour());
cd0b1709 809
4e9da8b7
RD
810 // first, draw the background
811 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
812 FillRect(hdc, &rectBtn, hbrushBackground);
813 ::DeleteObject(hbrushBackground);
cd0b1709 814
4e9da8b7
RD
815 // draw the border for the current state
816 bool selected = (state & ODS_SELECTED) != 0;
817 if ( !selected )
cd0b1709 818 {
6c20e8f8
VZ
819 wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
820 if ( tlw )
4e9da8b7 821 {
6c20e8f8 822 selected = tlw->GetDefaultItem() == this;
4e9da8b7 823 }
cd0b1709 824 }
4e9da8b7 825 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
cd0b1709 826
4e9da8b7 827 DrawButtonFrame(hdc, rectBtn, selected, pushed);
cd0b1709 828
4e9da8b7
RD
829 // if focused and !nofocus rect
830 if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) )
831 {
832 RECT rectFocus;
833 CopyRect(&rectFocus, &rectBtn);
cd0b1709 834
4e9da8b7
RD
835 // I don't know where does this constant come from, but this is how
836 // Windows draws them
837 InflateRect(&rectFocus, -4, -4);
cd0b1709 838
4e9da8b7
RD
839 DrawFocusRect(hdc, &rectFocus);
840 }
cd0b1709 841
4e9da8b7
RD
842 if ( pushed )
843 {
844 // the label is shifted by 1 pixel to create "pushed" effect
845 OffsetRect(&rectBtn, 1, 1);
846 }
9750fc42
VZ
847 }
848
4e9da8b7 849 COLORREF colFg = wxColourToRGB(GetForegroundColour());
047aa296
CE
850 if ( state & ODS_DISABLED ) colFg = GetSysColor(COLOR_GRAYTEXT) ;
851 wxString label = GetLabel();
852 if ( state & ODS_NOACCEL ) label = GetLabelText() ;
853 DrawButtonText(hdc, &rectBtn, label, colFg);
cd0b1709 854
fcf90ee1 855 return true;
cd0b1709
VZ
856}
857
858#endif // __WIN32__
1e6feb95
VZ
859
860#endif // wxUSE_BUTTON