]> git.saurik.com Git - wxWidgets.git/blob - src/msw/button.cpp
survive delete within Notify
[wxWidgets.git] / src / msw / button.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: msw/button.cpp
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
9 // Licence: wxWindows license
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "button.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #if wxUSE_BUTTON
32
33 #ifndef WX_PRECOMP
34 #include "wx/button.h"
35 #include "wx/brush.h"
36 #include "wx/panel.h"
37 #include "wx/bmpbuttn.h"
38 #include "wx/settings.h"
39 #include "wx/dcscreen.h"
40 #endif
41
42 #include "wx/msw/private.h"
43
44 // ----------------------------------------------------------------------------
45 // macros
46 // ----------------------------------------------------------------------------
47
48 IMPLEMENT_DYNAMIC_CLASS(wxButton, wxControl)
49
50 // this macro tries to adjust the default button height to a reasonable value
51 // using the char height as the base
52 #define BUTTON_HEIGHT_FROM_CHAR_HEIGHT(cy) (11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy)/10)
53
54 // ============================================================================
55 // implementation
56 // ============================================================================
57
58 // ----------------------------------------------------------------------------
59 // creation/destruction
60 // ----------------------------------------------------------------------------
61
62 bool 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)
70 {
71 if ( !CreateControl(parent, id, pos, size, style, validator, name) )
72 return FALSE;
73
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);
92 }
93
94 wxButton::~wxButton()
95 {
96 }
97
98 // ----------------------------------------------------------------------------
99 // flags
100 // ----------------------------------------------------------------------------
101
102 WXDWORD 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 );
109
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;
114
115 #ifdef __WIN32__
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 )
119 msStyle |= BS_LEFT;
120 if ( style & wxBU_RIGHT )
121 msStyle |= BS_RIGHT;
122 if ( style & wxBU_TOP )
123 msStyle |= BS_TOP;
124 if ( style & wxBU_BOTTOM )
125 msStyle |= BS_BOTTOM;
126 #endif // __WIN32__
127
128 return msStyle;
129 }
130
131 // ----------------------------------------------------------------------------
132 // size management including autosizing
133 // ----------------------------------------------------------------------------
134
135 wxSize wxButton::DoGetBestSize() const
136 {
137 wxString label = wxGetWindowText(GetHWND());
138 int wBtn;
139 GetTextExtent(label, &wBtn, NULL);
140
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);
149
150 wxSize sz = GetDefaultSize();
151 if (wBtn > sz.x) sz.x = wBtn;
152 if (hBtn > sz.y) sz.y = hBtn;
153
154 return sz;
155 }
156
157 /* static */
158 wxSize wxButtonBase::GetDefaultSize()
159 {
160 static wxSize s_sizeBtn;
161
162 if ( s_sizeBtn.x == 0 )
163 {
164 wxScreenDC dc;
165 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
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 }
178
179 return s_sizeBtn;
180 }
181
182 // ----------------------------------------------------------------------------
183 // default button handling
184 // ----------------------------------------------------------------------------
185
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
214 // set this button as the (permanently) default one in its panel
215 void wxButton::SetDefault()
216 {
217 wxWindow *parent = GetParent();
218
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
228 void 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);
239 }
240 //else: no styles to update
241 }
242
243 void 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 )
253 {
254 UpdateDefaultStyle(winOldDefault, this);
255 }
256 //else: we had been default before anyhow
257 }
258
259 /* static */
260 void
261 wxButton::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 )
266 {
267 // remove the BS_DEFPUSHBUTTON style from the other button
268 long style = ::GetWindowLong(GetHwndOf(btnOldDefault), GWL_STYLE);
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)!
272 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
273 {
274 style &= ~BS_DEFPUSHBUTTON;
275 ::SendMessage(GetHwndOf(btnOldDefault), BM_SETSTYLE, style, 1L);
276 }
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 }
283 }
284
285 // and set BS_DEFPUSHBUTTON for this button
286 wxButton *btnDefault = wxDynamicCast(winDefault, wxButton);
287 if ( btnDefault )
288 {
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 }
299 }
300 }
301
302 // ----------------------------------------------------------------------------
303 // helpers
304 // ----------------------------------------------------------------------------
305
306 bool wxButton::SendClickEvent()
307 {
308 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
309 event.SetEventObject(this);
310
311 return ProcessCommand(event);
312 }
313
314 void wxButton::Command(wxCommandEvent & event)
315 {
316 ProcessCommand(event);
317 }
318
319 // ----------------------------------------------------------------------------
320 // event/message handlers
321 // ----------------------------------------------------------------------------
322
323 bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
324 {
325 bool processed = FALSE;
326 switch ( param )
327 {
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
331 processed = SendClickEvent();
332 break;
333 }
334
335 return processed;
336 }
337
338 long wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
339 {
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
343 if ( nMsg == WM_SETFOCUS )
344 {
345 SetTmpDefault();
346
347 // let the default processing take place too
348 }
349 else if ( nMsg == WM_KILLFOCUS )
350 {
351 UnsetTmpDefault();
352 }
353 else if ( nMsg == WM_LBUTTONDBLCLK )
354 {
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
359 // and continue with processing the message normally as well
360 }
361
362 // let the base class do all real processing
363 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
364 }
365
366 // ----------------------------------------------------------------------------
367 // owner-drawn buttons support
368 // ----------------------------------------------------------------------------
369
370 #ifdef __WIN32__
371
372 // drawing helpers
373
374 static 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
389 static 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
398 void wxButton::MakeOwnerDrawn()
399 {
400 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
401 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
402 {
403 // make it so
404 style |= BS_OWNERDRAW;
405 SetWindowLong(GetHwnd(), GWL_STYLE, style);
406 }
407 }
408
409 bool 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
424 bool 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
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
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
456 BWHHHHHHHHHHHHHHGBB
457 BWH GBB
458 BWH GBB
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
470 BG GB
471 BGGGGGGGGGGGGGGGGGB
472 BBBBBBBBBBBBBBBBBBB
473 */
474
475 static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
476 bool selected, bool pushed)
477 {
478 RECT r;
479 CopyRect(&r, &rectBtn);
480
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));
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
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
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);
531 DeleteObject(hpenLightGr);
532 DeleteObject(hpenGrey);
533 DeleteObject(hpenBlack);
534 }
535
536 bool 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
581 if ( pushed )
582 {
583 // the label is shifted by 1 pixel to create "pushed" effect
584 OffsetRect(&rectBtn, 1, 1);
585 }
586
587 DrawButtonText(hdc, &rectBtn, GetLabel(),
588 state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT)
589 : colFg);
590
591 ::DeleteObject(hbrushBackground);
592
593 return TRUE;
594 }
595
596 #endif // __WIN32__
597
598 #endif // wxUSE_BUTTON
599