]> git.saurik.com Git - wxWidgets.git/blame - src/msw/button.cpp
a better fix for notebook page not being refreshed after Delete()
[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 137 int wBtn;
1e023926 138 GetTextExtent(wxGetWindowText(GetHWND()), &wBtn, NULL);
edccf428 139
4438caf4
VZ
140 int wChar, hChar;
141 wxGetCharSize(GetHWND(), &wChar, &hChar, &GetFont());
142
1e023926 143 // add a margin -- the button is wider than just its label
4438caf4
VZ
144 wBtn += 3*wChar;
145
146 // the button height is proportional to the height of the font used
147 int hBtn = BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hChar);
edccf428 148
1e023926
VZ
149 // all buttons have at least the standard size unless the user explicitly
150 // wants them to be of smaller size and used wxBU_EXACTFIT style when
151 // creating the button
152 if ( !HasFlag(wxBU_EXACTFIT) )
188b9f2e
RD
153 {
154 wxSize sz = GetDefaultSize();
1e023926
VZ
155 if (wBtn > sz.x)
156 sz.x = wBtn;
157 if (hBtn > sz.y)
158 sz.y = hBtn;
159
188b9f2e
RD
160 return sz;
161 }
a63f2bec 162
1e023926 163 return wxSize(wBtn, hBtn);
2bda0e17
KB
164}
165
e1f36ff8 166/* static */
1e6feb95 167wxSize wxButtonBase::GetDefaultSize()
e1f36ff8 168{
8c3c31d4 169 static wxSize s_sizeBtn;
e1f36ff8 170
8c3c31d4
VZ
171 if ( s_sizeBtn.x == 0 )
172 {
173 wxScreenDC dc;
a756f210 174 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
8c3c31d4
VZ
175
176 // the size of a standard button in the dialog units is 50x14,
177 // translate this to pixels
178 // NB1: the multipliers come from the Windows convention
179 // NB2: the extra +1/+2 were needed to get the size be the same as the
180 // size of the buttons in the standard dialog - I don't know how
181 // this happens, but on my system this size is 75x23 in pixels and
182 // 23*8 isn't even divisible by 14... Would be nice to understand
183 // why these constants are needed though!
184 s_sizeBtn.x = (50 * (dc.GetCharWidth() + 1))/4;
185 s_sizeBtn.y = ((14 * dc.GetCharHeight()) + 2)/8;
186 }
e1f36ff8 187
8c3c31d4 188 return s_sizeBtn;
e1f36ff8
VZ
189}
190
4438caf4 191// ----------------------------------------------------------------------------
036da5e3 192// default button handling
4438caf4
VZ
193// ----------------------------------------------------------------------------
194
d78f09e2
VZ
195/*
196 "Everything you ever wanted to know about the default buttons" or "Why do we
197 have to do all this?"
198
199 In MSW the default button should be activated when the user presses Enter
200 and the current control doesn't process Enter itself somehow. This is
201 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
202 Another aspect of "defaultness" is that the default button has different
203 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
204 separate from DM_SETDEFID stuff (!).
205
206 Final complication is that when a button is active, it should be the default
207 one, i.e. pressing Enter on a button always activates it and not another
208 one.
209
210 We handle this by maintaining a permanent and a temporary default items in
211 wxControlContainer (both may be NULL). When a button becomes the current
212 control (i.e. gets focus) it sets itself as the temporary default which
213 ensures that it has the right appearance and that Enter will be redirected
214 to it. When the button loses focus, it unsets the temporary default and so
215 the default item will be the permanent default -- that is the default button
216 if any had been set or none otherwise, which is just what we want.
217
218 Remark that we probably don't need to send DM_SETDEFID as we don't use
219 ::IsDialogMessage() (which relies on it) any longer but OTOH it probably
220 doesn't hurt neither.
221 */
222
036da5e3 223// set this button as the (permanently) default one in its panel
edccf428 224void wxButton::SetDefault()
2bda0e17 225{
edccf428 226 wxWindow *parent = GetParent();
2bda0e17 227
036da5e3
VZ
228 wxCHECK_RET( parent, _T("button without parent?") );
229
230 // set this one as the default button both for wxWindows and Windows
231 wxWindow *winOldDefault = parent->SetDefaultItem(this);
232 ::SendMessage(GetWinHwnd(parent), DM_SETDEFID, m_windowId, 0L);
233
234 UpdateDefaultStyle(this, winOldDefault);
235}
236
237void wxButton::SetTmpDefault()
238{
239 wxWindow *parent = GetParent();
240
241 wxCHECK_RET( parent, _T("button without parent?") );
242
243 wxWindow *winOldDefault = parent->GetDefaultItem();
244 parent->SetTmpDefaultItem(this);
245 if ( winOldDefault != this )
246 {
247 UpdateDefaultStyle(this, winOldDefault);
456bc6d9 248 }
036da5e3
VZ
249 //else: no styles to update
250}
251
252void wxButton::UnsetTmpDefault()
253{
254 wxWindow *parent = GetParent();
255
256 wxCHECK_RET( parent, _T("button without parent?") );
257
258 parent->SetTmpDefaultItem(NULL);
259
260 wxWindow *winOldDefault = parent->GetDefaultItem();
261 if ( winOldDefault != this )
edccf428 262 {
036da5e3 263 UpdateDefaultStyle(winOldDefault, this);
edccf428 264 }
036da5e3
VZ
265 //else: we had been default before anyhow
266}
8ed57d93 267
036da5e3
VZ
268/* static */
269void
270wxButton::UpdateDefaultStyle(wxWindow *winDefault, wxWindow *winOldDefault)
271{
272 // clear the BS_DEFPUSHBUTTON for the old default button
273 wxButton *btnOldDefault = wxDynamicCast(winOldDefault, wxButton);
274 if ( btnOldDefault && btnOldDefault != winDefault )
5d1d2d46
VZ
275 {
276 // remove the BS_DEFPUSHBUTTON style from the other button
036da5e3 277 long style = ::GetWindowLong(GetHwndOf(btnOldDefault), GWL_STYLE);
cd0b1709
VZ
278
279 // don't do it with the owner drawn buttons because it will reset
280 // BS_OWNERDRAW style bit too (BS_OWNERDRAW & BS_DEFPUSHBUTTON != 0)!
be4017f8 281 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
cd0b1709
VZ
282 {
283 style &= ~BS_DEFPUSHBUTTON;
036da5e3 284 ::SendMessage(GetHwndOf(btnOldDefault), BM_SETSTYLE, style, 1L);
cd0b1709 285 }
be4017f8
VZ
286 else
287 {
288 // redraw the button - it will notice itself that it's not the
289 // default one any longer
290 btnOldDefault->Refresh();
291 }
5d1d2d46
VZ
292 }
293
036da5e3
VZ
294 // and set BS_DEFPUSHBUTTON for this button
295 wxButton *btnDefault = wxDynamicCast(winDefault, wxButton);
296 if ( btnDefault )
be4017f8 297 {
036da5e3
VZ
298 long style = ::GetWindowLong(GetHwndOf(btnDefault), GWL_STYLE);
299 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
300 {
301 style |= BS_DEFPUSHBUTTON;
302 ::SendMessage(GetHwndOf(btnDefault), BM_SETSTYLE, style, 1L);
303 }
304 else
305 {
306 btnDefault->Refresh();
307 }
be4017f8 308 }
2bda0e17
KB
309}
310
edccf428
VZ
311// ----------------------------------------------------------------------------
312// helpers
313// ----------------------------------------------------------------------------
314
315bool wxButton::SendClickEvent()
2bda0e17 316{
edccf428
VZ
317 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
318 event.SetEventObject(this);
319
320 return ProcessCommand(event);
2bda0e17
KB
321}
322
edccf428 323void wxButton::Command(wxCommandEvent & event)
2bda0e17 324{
edccf428 325 ProcessCommand(event);
2bda0e17
KB
326}
327
edccf428
VZ
328// ----------------------------------------------------------------------------
329// event/message handlers
330// ----------------------------------------------------------------------------
2bda0e17 331
33ac7e6f 332bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
edccf428
VZ
333{
334 bool processed = FALSE;
57c0af52 335 switch ( param )
edccf428 336 {
a95e38c0
VZ
337 case 1: // message came from an accelerator
338 case BN_CLICKED: // normal buttons send this
339 case BN_DOUBLECLICKED: // owner-drawn ones also send this
57c0af52
VZ
340 processed = SendClickEvent();
341 break;
edccf428 342 }
2bda0e17 343
edccf428 344 return processed;
2bda0e17
KB
345}
346
678cd6de
VZ
347long wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
348{
036da5e3
VZ
349 // when we receive focus, we want to temporary become the default button in
350 // our parent panel so that pressing "Enter" would activate us -- and when
351 // losing it we should restore the previous default button as well
be4017f8 352 if ( nMsg == WM_SETFOCUS )
678cd6de 353 {
036da5e3 354 SetTmpDefault();
be4017f8 355
036da5e3
VZ
356 // let the default processing take place too
357 }
358 else if ( nMsg == WM_KILLFOCUS )
359 {
360 UnsetTmpDefault();
678cd6de 361 }
a95e38c0
VZ
362 else if ( nMsg == WM_LBUTTONDBLCLK )
363 {
f6bcfd97
BP
364 // emulate a click event to force an owner-drawn button to change its
365 // appearance - without this, it won't do it
366 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN, wParam, lParam);
367
036da5e3 368 // and continue with processing the message normally as well
a95e38c0 369 }
678cd6de
VZ
370
371 // let the base class do all real processing
372 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
373}
cd0b1709
VZ
374
375// ----------------------------------------------------------------------------
376// owner-drawn buttons support
377// ----------------------------------------------------------------------------
378
379#ifdef __WIN32__
380
381// drawing helpers
382
383static void DrawButtonText(HDC hdc,
384 RECT *pRect,
385 const wxString& text,
386 COLORREF col)
387{
388 COLORREF colOld = SetTextColor(hdc, col);
389 int modeOld = SetBkMode(hdc, TRANSPARENT);
390
391 DrawText(hdc, text, text.length(), pRect,
392 DT_CENTER | DT_VCENTER | DT_SINGLELINE);
393
394 SetBkMode(hdc, modeOld);
395 SetTextColor(hdc, colOld);
396}
397
398static void DrawRect(HDC hdc, const RECT& r)
399{
400 MoveToEx(hdc, r.left, r.top, NULL);
401 LineTo(hdc, r.right, r.top);
402 LineTo(hdc, r.right, r.bottom);
403 LineTo(hdc, r.left, r.bottom);
404 LineTo(hdc, r.left, r.top);
405}
406
407void wxButton::MakeOwnerDrawn()
408{
409 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
9750fc42 410 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
cd0b1709
VZ
411 {
412 // make it so
413 style |= BS_OWNERDRAW;
414 SetWindowLong(GetHwnd(), GWL_STYLE, style);
415 }
416}
417
418bool wxButton::SetBackgroundColour(const wxColour &colour)
419{
420 if ( !wxControl::SetBackgroundColour(colour) )
421 {
422 // nothing to do
423 return FALSE;
424 }
425
426 MakeOwnerDrawn();
427
428 Refresh();
429
430 return TRUE;
431}
432
433bool wxButton::SetForegroundColour(const wxColour &colour)
434{
435 if ( !wxControl::SetForegroundColour(colour) )
436 {
437 // nothing to do
438 return FALSE;
439 }
440
441 MakeOwnerDrawn();
442
443 Refresh();
444
445 return TRUE;
446}
447
448/*
449 The button frame looks like this normally:
450
451 WWWWWWWWWWWWWWWWWWB
16162a64
GRG
452 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
453 WH GB H = light grey (LIGHT)
454 WH GB G = dark grey (SHADOW)
455 WH GB B = black (DKSHADOW)
456 WH GB
cd0b1709
VZ
457 WGGGGGGGGGGGGGGGGGB
458 BBBBBBBBBBBBBBBBBBB
459
460 When the button is selected, the button becomes like this (the total button
461 size doesn't change):
462
463 BBBBBBBBBBBBBBBBBBB
464 BWWWWWWWWWWWWWWWWBB
16162a64
GRG
465 BWHHHHHHHHHHHHHHGBB
466 BWH GBB
467 BWH GBB
cd0b1709
VZ
468 BWGGGGGGGGGGGGGGGBB
469 BBBBBBBBBBBBBBBBBBB
470 BBBBBBBBBBBBBBBBBBB
471
472 When the button is pushed (while selected) it is like:
473
474 BBBBBBBBBBBBBBBBBBB
475 BGGGGGGGGGGGGGGGGGB
476 BG GB
477 BG GB
478 BG GB
16162a64 479 BG GB
cd0b1709
VZ
480 BGGGGGGGGGGGGGGGGGB
481 BBBBBBBBBBBBBBBBBBB
482*/
483
484static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
485 bool selected, bool pushed)
486{
487 RECT r;
488 CopyRect(&r, &rectBtn);
489
16162a64
GRG
490 HPEN hpenBlack = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW)),
491 hpenGrey = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW)),
492 hpenLightGr = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT)),
493 hpenWhite = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT));
cd0b1709
VZ
494
495 HPEN hpenOld = (HPEN)SelectObject(hdc, hpenBlack);
496
497 r.right--;
498 r.bottom--;
499
500 if ( pushed )
501 {
502 DrawRect(hdc, r);
503
504 (void)SelectObject(hdc, hpenGrey);
505 InflateRect(&r, -1, -1);
506
507 DrawRect(hdc, r);
508 }
509 else // !pushed
510 {
511 if ( selected )
512 {
513 DrawRect(hdc, r);
514
515 InflateRect(&r, -1, -1);
516 }
517
518 MoveToEx(hdc, r.left, r.bottom, NULL);
519 LineTo(hdc, r.right, r.bottom);
520 LineTo(hdc, r.right, r.top - 1);
521
522 (void)SelectObject(hdc, hpenWhite);
523 MoveToEx(hdc, r.left, r.bottom - 1, NULL);
524 LineTo(hdc, r.left, r.top);
525 LineTo(hdc, r.right, r.top);
526
16162a64
GRG
527 (void)SelectObject(hdc, hpenLightGr);
528 MoveToEx(hdc, r.left + 1, r.bottom - 2, NULL);
529 LineTo(hdc, r.left + 1, r.top + 1);
530 LineTo(hdc, r.right - 1, r.top + 1);
531
cd0b1709
VZ
532 (void)SelectObject(hdc, hpenGrey);
533 MoveToEx(hdc, r.left + 1, r.bottom - 1, NULL);
534 LineTo(hdc, r.right - 1, r.bottom - 1);
535 LineTo(hdc, r.right - 1, r.top);
536 }
537
538 (void)SelectObject(hdc, hpenOld);
539 DeleteObject(hpenWhite);
16162a64 540 DeleteObject(hpenLightGr);
cd0b1709
VZ
541 DeleteObject(hpenGrey);
542 DeleteObject(hpenBlack);
543}
544
545bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
546{
547 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
548
549 RECT rectBtn;
550 CopyRect(&rectBtn, &lpDIS->rcItem);
551
552 COLORREF colBg = wxColourToRGB(GetBackgroundColour()),
553 colFg = wxColourToRGB(GetForegroundColour());
554
555 HDC hdc = lpDIS->hDC;
556 UINT state = lpDIS->itemState;
557
558 // first, draw the background
559 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
560
561 FillRect(hdc, &rectBtn, hbrushBackground);
562
563 // draw the border for the current state
564 bool selected = (state & ODS_SELECTED) != 0;
565 if ( !selected )
566 {
567 wxPanel *panel = wxDynamicCast(GetParent(), wxPanel);
568 if ( panel )
569 {
570 selected = panel->GetDefaultItem() == this;
571 }
572 }
573 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
574
575 DrawButtonFrame(hdc, rectBtn, selected, pushed);
576
577 // draw the focus rect if needed
578 if ( state & ODS_FOCUS )
579 {
580 RECT rectFocus;
581 CopyRect(&rectFocus, &rectBtn);
582
583 // I don't know where does this constant come from, but this is how
584 // Windows draws them
585 InflateRect(&rectFocus, -4, -4);
586
587 DrawFocusRect(hdc, &rectFocus);
588 }
589
9750fc42
VZ
590 if ( pushed )
591 {
592 // the label is shifted by 1 pixel to create "pushed" effect
593 OffsetRect(&rectBtn, 1, 1);
594 }
595
cd0b1709
VZ
596 DrawButtonText(hdc, &rectBtn, GetLabel(),
597 state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT)
598 : colFg);
599
600 ::DeleteObject(hbrushBackground);
601
cd0b1709
VZ
602 return TRUE;
603}
604
605#endif // __WIN32__
1e6feb95
VZ
606
607#endif // wxUSE_BUTTON
608