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