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