]> git.saurik.com Git - wxWidgets.git/blame - src/msw/button.cpp
new wxASSERT implementation using wxAssert() helper function
[wxWidgets.git] / src / msw / button.cpp
CommitLineData
2bda0e17 1/////////////////////////////////////////////////////////////////////////////
cd0b1709 2// Name: msw/button.cpp
2bda0e17
KB
3// Purpose: wxButton
4// Author: Julian Smart
5// Modified by:
6// Created: 04/01/98
7// RCS-ID: $Id$
8// Copyright: (c) Julian Smart and Markus Holzem
edccf428 9// Licence: wxWindows license
2bda0e17
KB
10/////////////////////////////////////////////////////////////////////////////
11
edccf428
VZ
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
cd0b1709 19
2bda0e17 20#ifdef __GNUG__
edccf428 21 #pragma implementation "button.h"
2bda0e17
KB
22#endif
23
24// For compilers that support precompilation, includes "wx.h".
25#include "wx/wxprec.h"
26
27#ifdef __BORLANDC__
edccf428 28 #pragma hdrstop
2bda0e17
KB
29#endif
30
1e6feb95
VZ
31#if wxUSE_BUTTON
32
2bda0e17 33#ifndef WX_PRECOMP
edccf428
VZ
34 #include "wx/button.h"
35 #include "wx/brush.h"
4e938f5b 36 #include "wx/panel.h"
8a4df159 37 #include "wx/bmpbuttn.h"
fb39c7ec
RR
38 #include "wx/settings.h"
39 #include "wx/dcscreen.h"
2bda0e17
KB
40#endif
41
42#include "wx/msw/private.h"
43
edccf428
VZ
44// ----------------------------------------------------------------------------
45// macros
46// ----------------------------------------------------------------------------
47
cd0b1709 48IMPLEMENT_DYNAMIC_CLASS(wxButton, wxControl)
2bda0e17 49
edccf428
VZ
50// this macro tries to adjust the default button height to a reasonable value
51// using the char height as the base
1c4a764c 52#define BUTTON_HEIGHT_FROM_CHAR_HEIGHT(cy) (11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy)/10)
2bda0e17 53
edccf428
VZ
54// ============================================================================
55// implementation
56// ============================================================================
57
58// ----------------------------------------------------------------------------
59// creation/destruction
60// ----------------------------------------------------------------------------
61
62bool wxButton::Create(wxWindow *parent,
63 wxWindowID id,
64 const wxString& label,
65 const wxPoint& pos,
66 const wxSize& size,
67 long style,
68 const wxValidator& validator,
69 const wxString& name)
2bda0e17 70{
5b2f31eb 71 if ( !CreateControl(parent, id, pos, size, style, validator, name) )
edccf428
VZ
72 return FALSE;
73
8292017c
VZ
74 WXDWORD exstyle;
75 WXDWORD msStyle = MSWGetStyle(style, &exstyle);
76
77#ifdef __WIN32__
78 // if the label contains several lines we must explicitly tell the button
79 // about it or it wouldn't draw it correctly ("\n"s would just appear as
80 // black boxes)
81 //
82 // NB: we do it here and not in MSWGetStyle() because we need the label
83 // value and m_label is not set yet when MSWGetStyle() is called;
84 // besides changing BS_MULTILINE during run-time is pointless anyhow
85 if ( label.find(_T('\n')) != wxString::npos )
86 {
87 msStyle |= BS_MULTILINE;
88 }
89#endif // __WIN32__
90
91 return MSWCreateControl(_T("BUTTON"), msStyle, pos, size, label, exstyle);
5b2f31eb
VZ
92}
93
94wxButton::~wxButton()
95{
96}
edccf428 97
5b2f31eb
VZ
98// ----------------------------------------------------------------------------
99// flags
100// ----------------------------------------------------------------------------
cd0b1709 101
5b2f31eb
VZ
102WXDWORD wxButton::MSWGetStyle(long style, WXDWORD *exstyle) const
103{
104 // buttons never have an external border, they draw their own one
105 WXDWORD msStyle = wxControl::MSWGetStyle
106 (
107 (style & ~wxBORDER_MASK) | wxBORDER_NONE, exstyle
108 );
f6bcfd97 109
5b2f31eb
VZ
110 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
111 // each other in any resizeable dialog which has more than one button in
112 // the bottom
113 msStyle |= WS_CLIPSIBLINGS;
b0766406 114
f6bcfd97 115#ifdef __WIN32__
5b2f31eb
VZ
116 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
117 // and wxBU_RIGHT to get BS_CENTER!
118 if ( style & wxBU_LEFT )
f6bcfd97 119 msStyle |= BS_LEFT;
5b2f31eb 120 if ( style & wxBU_RIGHT )
f6bcfd97 121 msStyle |= BS_RIGHT;
5b2f31eb 122 if ( style & wxBU_TOP )
f6bcfd97 123 msStyle |= BS_TOP;
5b2f31eb 124 if ( style & wxBU_BOTTOM )
f6bcfd97 125 msStyle |= BS_BOTTOM;
5b2f31eb 126#endif // __WIN32__
edccf428 127
5b2f31eb 128 return msStyle;
2bda0e17
KB
129}
130
edccf428
VZ
131// ----------------------------------------------------------------------------
132// size management including autosizing
133// ----------------------------------------------------------------------------
134
f68586e5 135wxSize wxButton::DoGetBestSize() const
2bda0e17 136{
4438caf4
VZ
137 wxString label = wxGetWindowText(GetHWND());
138 int wBtn;
139 GetTextExtent(label, &wBtn, NULL);
edccf428 140
4438caf4
VZ
141 int wChar, hChar;
142 wxGetCharSize(GetHWND(), &wChar, &hChar, &GetFont());
143
144 // add a margin - the button is wider than just its label
145 wBtn += 3*wChar;
146
147 // the button height is proportional to the height of the font used
148 int hBtn = BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hChar);
edccf428 149
a63f2bec
VS
150 wxSize sz = GetDefaultSize();
151 if (wBtn > sz.x) sz.x = wBtn;
152 if (hBtn > sz.y) sz.y = hBtn;
153
154 return sz;
2bda0e17
KB
155}
156
e1f36ff8 157/* static */
1e6feb95 158wxSize wxButtonBase::GetDefaultSize()
e1f36ff8 159{
8c3c31d4 160 static wxSize s_sizeBtn;
e1f36ff8 161
8c3c31d4
VZ
162 if ( s_sizeBtn.x == 0 )
163 {
164 wxScreenDC dc;
a756f210 165 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
8c3c31d4
VZ
166
167 // the size of a standard button in the dialog units is 50x14,
168 // translate this to pixels
169 // NB1: the multipliers come from the Windows convention
170 // NB2: the extra +1/+2 were needed to get the size be the same as the
171 // size of the buttons in the standard dialog - I don't know how
172 // this happens, but on my system this size is 75x23 in pixels and
173 // 23*8 isn't even divisible by 14... Would be nice to understand
174 // why these constants are needed though!
175 s_sizeBtn.x = (50 * (dc.GetCharWidth() + 1))/4;
176 s_sizeBtn.y = ((14 * dc.GetCharHeight()) + 2)/8;
177 }
e1f36ff8 178
8c3c31d4 179 return s_sizeBtn;
e1f36ff8
VZ
180}
181
4438caf4 182// ----------------------------------------------------------------------------
036da5e3 183// default button handling
4438caf4
VZ
184// ----------------------------------------------------------------------------
185
d78f09e2
VZ
186/*
187 "Everything you ever wanted to know about the default buttons" or "Why do we
188 have to do all this?"
189
190 In MSW the default button should be activated when the user presses Enter
191 and the current control doesn't process Enter itself somehow. This is
192 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
193 Another aspect of "defaultness" is that the default button has different
194 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
195 separate from DM_SETDEFID stuff (!).
196
197 Final complication is that when a button is active, it should be the default
198 one, i.e. pressing Enter on a button always activates it and not another
199 one.
200
201 We handle this by maintaining a permanent and a temporary default items in
202 wxControlContainer (both may be NULL). When a button becomes the current
203 control (i.e. gets focus) it sets itself as the temporary default which
204 ensures that it has the right appearance and that Enter will be redirected
205 to it. When the button loses focus, it unsets the temporary default and so
206 the default item will be the permanent default -- that is the default button
207 if any had been set or none otherwise, which is just what we want.
208
209 Remark that we probably don't need to send DM_SETDEFID as we don't use
210 ::IsDialogMessage() (which relies on it) any longer but OTOH it probably
211 doesn't hurt neither.
212 */
213
036da5e3 214// set this button as the (permanently) default one in its panel
edccf428 215void wxButton::SetDefault()
2bda0e17 216{
edccf428 217 wxWindow *parent = GetParent();
2bda0e17 218
036da5e3
VZ
219 wxCHECK_RET( parent, _T("button without parent?") );
220
221 // set this one as the default button both for wxWindows and Windows
222 wxWindow *winOldDefault = parent->SetDefaultItem(this);
223 ::SendMessage(GetWinHwnd(parent), DM_SETDEFID, m_windowId, 0L);
224
225 UpdateDefaultStyle(this, winOldDefault);
226}
227
228void wxButton::SetTmpDefault()
229{
230 wxWindow *parent = GetParent();
231
232 wxCHECK_RET( parent, _T("button without parent?") );
233
234 wxWindow *winOldDefault = parent->GetDefaultItem();
235 parent->SetTmpDefaultItem(this);
236 if ( winOldDefault != this )
237 {
238 UpdateDefaultStyle(this, winOldDefault);
456bc6d9 239 }
036da5e3
VZ
240 //else: no styles to update
241}
242
243void wxButton::UnsetTmpDefault()
244{
245 wxWindow *parent = GetParent();
246
247 wxCHECK_RET( parent, _T("button without parent?") );
248
249 parent->SetTmpDefaultItem(NULL);
250
251 wxWindow *winOldDefault = parent->GetDefaultItem();
252 if ( winOldDefault != this )
edccf428 253 {
036da5e3 254 UpdateDefaultStyle(winOldDefault, this);
edccf428 255 }
036da5e3
VZ
256 //else: we had been default before anyhow
257}
8ed57d93 258
036da5e3
VZ
259/* static */
260void
261wxButton::UpdateDefaultStyle(wxWindow *winDefault, wxWindow *winOldDefault)
262{
263 // clear the BS_DEFPUSHBUTTON for the old default button
264 wxButton *btnOldDefault = wxDynamicCast(winOldDefault, wxButton);
265 if ( btnOldDefault && btnOldDefault != winDefault )
5d1d2d46
VZ
266 {
267 // remove the BS_DEFPUSHBUTTON style from the other button
036da5e3 268 long style = ::GetWindowLong(GetHwndOf(btnOldDefault), GWL_STYLE);
cd0b1709
VZ
269
270 // don't do it with the owner drawn buttons because it will reset
271 // BS_OWNERDRAW style bit too (BS_OWNERDRAW & BS_DEFPUSHBUTTON != 0)!
be4017f8 272 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
cd0b1709
VZ
273 {
274 style &= ~BS_DEFPUSHBUTTON;
036da5e3 275 ::SendMessage(GetHwndOf(btnOldDefault), BM_SETSTYLE, style, 1L);
cd0b1709 276 }
be4017f8
VZ
277 else
278 {
279 // redraw the button - it will notice itself that it's not the
280 // default one any longer
281 btnOldDefault->Refresh();
282 }
5d1d2d46
VZ
283 }
284
036da5e3
VZ
285 // and set BS_DEFPUSHBUTTON for this button
286 wxButton *btnDefault = wxDynamicCast(winDefault, wxButton);
287 if ( btnDefault )
be4017f8 288 {
036da5e3
VZ
289 long style = ::GetWindowLong(GetHwndOf(btnDefault), GWL_STYLE);
290 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
291 {
292 style |= BS_DEFPUSHBUTTON;
293 ::SendMessage(GetHwndOf(btnDefault), BM_SETSTYLE, style, 1L);
294 }
295 else
296 {
297 btnDefault->Refresh();
298 }
be4017f8 299 }
2bda0e17
KB
300}
301
edccf428
VZ
302// ----------------------------------------------------------------------------
303// helpers
304// ----------------------------------------------------------------------------
305
306bool wxButton::SendClickEvent()
2bda0e17 307{
edccf428
VZ
308 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
309 event.SetEventObject(this);
310
311 return ProcessCommand(event);
2bda0e17
KB
312}
313
edccf428 314void wxButton::Command(wxCommandEvent & event)
2bda0e17 315{
edccf428 316 ProcessCommand(event);
2bda0e17
KB
317}
318
edccf428
VZ
319// ----------------------------------------------------------------------------
320// event/message handlers
321// ----------------------------------------------------------------------------
2bda0e17 322
33ac7e6f 323bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
edccf428
VZ
324{
325 bool processed = FALSE;
57c0af52 326 switch ( param )
edccf428 327 {
a95e38c0
VZ
328 case 1: // message came from an accelerator
329 case BN_CLICKED: // normal buttons send this
330 case BN_DOUBLECLICKED: // owner-drawn ones also send this
57c0af52
VZ
331 processed = SendClickEvent();
332 break;
edccf428 333 }
2bda0e17 334
edccf428 335 return processed;
2bda0e17
KB
336}
337
678cd6de
VZ
338long wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
339{
036da5e3
VZ
340 // when we receive focus, we want to temporary become the default button in
341 // our parent panel so that pressing "Enter" would activate us -- and when
342 // losing it we should restore the previous default button as well
be4017f8 343 if ( nMsg == WM_SETFOCUS )
678cd6de 344 {
036da5e3 345 SetTmpDefault();
be4017f8 346
036da5e3
VZ
347 // let the default processing take place too
348 }
349 else if ( nMsg == WM_KILLFOCUS )
350 {
351 UnsetTmpDefault();
678cd6de 352 }
a95e38c0
VZ
353 else if ( nMsg == WM_LBUTTONDBLCLK )
354 {
f6bcfd97
BP
355 // emulate a click event to force an owner-drawn button to change its
356 // appearance - without this, it won't do it
357 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN, wParam, lParam);
358
036da5e3 359 // and continue with processing the message normally as well
a95e38c0 360 }
678cd6de
VZ
361
362 // let the base class do all real processing
363 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
364}
cd0b1709
VZ
365
366// ----------------------------------------------------------------------------
367// owner-drawn buttons support
368// ----------------------------------------------------------------------------
369
370#ifdef __WIN32__
371
372// drawing helpers
373
374static void DrawButtonText(HDC hdc,
375 RECT *pRect,
376 const wxString& text,
377 COLORREF col)
378{
379 COLORREF colOld = SetTextColor(hdc, col);
380 int modeOld = SetBkMode(hdc, TRANSPARENT);
381
382 DrawText(hdc, text, text.length(), pRect,
383 DT_CENTER | DT_VCENTER | DT_SINGLELINE);
384
385 SetBkMode(hdc, modeOld);
386 SetTextColor(hdc, colOld);
387}
388
389static void DrawRect(HDC hdc, const RECT& r)
390{
391 MoveToEx(hdc, r.left, r.top, NULL);
392 LineTo(hdc, r.right, r.top);
393 LineTo(hdc, r.right, r.bottom);
394 LineTo(hdc, r.left, r.bottom);
395 LineTo(hdc, r.left, r.top);
396}
397
398void wxButton::MakeOwnerDrawn()
399{
400 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
9750fc42 401 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
cd0b1709
VZ
402 {
403 // make it so
404 style |= BS_OWNERDRAW;
405 SetWindowLong(GetHwnd(), GWL_STYLE, style);
406 }
407}
408
409bool wxButton::SetBackgroundColour(const wxColour &colour)
410{
411 if ( !wxControl::SetBackgroundColour(colour) )
412 {
413 // nothing to do
414 return FALSE;
415 }
416
417 MakeOwnerDrawn();
418
419 Refresh();
420
421 return TRUE;
422}
423
424bool wxButton::SetForegroundColour(const wxColour &colour)
425{
426 if ( !wxControl::SetForegroundColour(colour) )
427 {
428 // nothing to do
429 return FALSE;
430 }
431
432 MakeOwnerDrawn();
433
434 Refresh();
435
436 return TRUE;
437}
438
439/*
440 The button frame looks like this normally:
441
442 WWWWWWWWWWWWWWWWWWB
16162a64
GRG
443 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
444 WH GB H = light grey (LIGHT)
445 WH GB G = dark grey (SHADOW)
446 WH GB B = black (DKSHADOW)
447 WH GB
cd0b1709
VZ
448 WGGGGGGGGGGGGGGGGGB
449 BBBBBBBBBBBBBBBBBBB
450
451 When the button is selected, the button becomes like this (the total button
452 size doesn't change):
453
454 BBBBBBBBBBBBBBBBBBB
455 BWWWWWWWWWWWWWWWWBB
16162a64
GRG
456 BWHHHHHHHHHHHHHHGBB
457 BWH GBB
458 BWH GBB
cd0b1709
VZ
459 BWGGGGGGGGGGGGGGGBB
460 BBBBBBBBBBBBBBBBBBB
461 BBBBBBBBBBBBBBBBBBB
462
463 When the button is pushed (while selected) it is like:
464
465 BBBBBBBBBBBBBBBBBBB
466 BGGGGGGGGGGGGGGGGGB
467 BG GB
468 BG GB
469 BG GB
16162a64 470 BG GB
cd0b1709
VZ
471 BGGGGGGGGGGGGGGGGGB
472 BBBBBBBBBBBBBBBBBBB
473*/
474
475static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
476 bool selected, bool pushed)
477{
478 RECT r;
479 CopyRect(&r, &rectBtn);
480
16162a64
GRG
481 HPEN hpenBlack = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW)),
482 hpenGrey = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW)),
483 hpenLightGr = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT)),
484 hpenWhite = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT));
cd0b1709
VZ
485
486 HPEN hpenOld = (HPEN)SelectObject(hdc, hpenBlack);
487
488 r.right--;
489 r.bottom--;
490
491 if ( pushed )
492 {
493 DrawRect(hdc, r);
494
495 (void)SelectObject(hdc, hpenGrey);
496 InflateRect(&r, -1, -1);
497
498 DrawRect(hdc, r);
499 }
500 else // !pushed
501 {
502 if ( selected )
503 {
504 DrawRect(hdc, r);
505
506 InflateRect(&r, -1, -1);
507 }
508
509 MoveToEx(hdc, r.left, r.bottom, NULL);
510 LineTo(hdc, r.right, r.bottom);
511 LineTo(hdc, r.right, r.top - 1);
512
513 (void)SelectObject(hdc, hpenWhite);
514 MoveToEx(hdc, r.left, r.bottom - 1, NULL);
515 LineTo(hdc, r.left, r.top);
516 LineTo(hdc, r.right, r.top);
517
16162a64
GRG
518 (void)SelectObject(hdc, hpenLightGr);
519 MoveToEx(hdc, r.left + 1, r.bottom - 2, NULL);
520 LineTo(hdc, r.left + 1, r.top + 1);
521 LineTo(hdc, r.right - 1, r.top + 1);
522
cd0b1709
VZ
523 (void)SelectObject(hdc, hpenGrey);
524 MoveToEx(hdc, r.left + 1, r.bottom - 1, NULL);
525 LineTo(hdc, r.right - 1, r.bottom - 1);
526 LineTo(hdc, r.right - 1, r.top);
527 }
528
529 (void)SelectObject(hdc, hpenOld);
530 DeleteObject(hpenWhite);
16162a64 531 DeleteObject(hpenLightGr);
cd0b1709
VZ
532 DeleteObject(hpenGrey);
533 DeleteObject(hpenBlack);
534}
535
536bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
537{
538 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
539
540 RECT rectBtn;
541 CopyRect(&rectBtn, &lpDIS->rcItem);
542
543 COLORREF colBg = wxColourToRGB(GetBackgroundColour()),
544 colFg = wxColourToRGB(GetForegroundColour());
545
546 HDC hdc = lpDIS->hDC;
547 UINT state = lpDIS->itemState;
548
549 // first, draw the background
550 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
551
552 FillRect(hdc, &rectBtn, hbrushBackground);
553
554 // draw the border for the current state
555 bool selected = (state & ODS_SELECTED) != 0;
556 if ( !selected )
557 {
558 wxPanel *panel = wxDynamicCast(GetParent(), wxPanel);
559 if ( panel )
560 {
561 selected = panel->GetDefaultItem() == this;
562 }
563 }
564 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
565
566 DrawButtonFrame(hdc, rectBtn, selected, pushed);
567
568 // draw the focus rect if needed
569 if ( state & ODS_FOCUS )
570 {
571 RECT rectFocus;
572 CopyRect(&rectFocus, &rectBtn);
573
574 // I don't know where does this constant come from, but this is how
575 // Windows draws them
576 InflateRect(&rectFocus, -4, -4);
577
578 DrawFocusRect(hdc, &rectFocus);
579 }
580
9750fc42
VZ
581 if ( pushed )
582 {
583 // the label is shifted by 1 pixel to create "pushed" effect
584 OffsetRect(&rectBtn, 1, 1);
585 }
586
cd0b1709
VZ
587 DrawButtonText(hdc, &rectBtn, GetLabel(),
588 state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT)
589 : colFg);
590
591 ::DeleteObject(hbrushBackground);
592
cd0b1709
VZ
593 return TRUE;
594}
595
596#endif // __WIN32__
1e6feb95
VZ
597
598#endif // wxUSE_BUTTON
599