]> git.saurik.com Git - wxWidgets.git/blob - src/msw/button.cpp
fixed a harmless warning (patch 595096) and added a comment
[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 int wBtn;
138 GetTextExtent(wxGetWindowText(GetHWND()), &wBtn, NULL);
139
140 int wChar, hChar;
141 wxGetCharSize(GetHWND(), &wChar, &hChar, &GetFont());
142
143 // add a margin -- the button is wider than just its label
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);
148
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) )
153 {
154 wxSize sz = GetDefaultSize();
155 if (wBtn > sz.x)
156 sz.x = wBtn;
157 if (hBtn > sz.y)
158 sz.y = hBtn;
159
160 return sz;
161 }
162
163 return wxSize(wBtn, hBtn);
164 }
165
166 /* static */
167 wxSize wxButtonBase::GetDefaultSize()
168 {
169 static wxSize s_sizeBtn;
170
171 if ( s_sizeBtn.x == 0 )
172 {
173 wxScreenDC dc;
174 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
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 }
187
188 return s_sizeBtn;
189 }
190
191 // ----------------------------------------------------------------------------
192 // default button handling
193 // ----------------------------------------------------------------------------
194
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
223 // set this button as the (permanently) default one in its panel
224 void wxButton::SetDefault()
225 {
226 wxWindow *parent = GetParent();
227
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
237 void 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);
248 }
249 //else: no styles to update
250 }
251
252 void 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 )
262 {
263 UpdateDefaultStyle(winOldDefault, this);
264 }
265 //else: we had been default before anyhow
266 }
267
268 /* static */
269 void
270 wxButton::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 )
275 {
276 // remove the BS_DEFPUSHBUTTON style from the other button
277 long style = ::GetWindowLong(GetHwndOf(btnOldDefault), GWL_STYLE);
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)!
281 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
282 {
283 style &= ~BS_DEFPUSHBUTTON;
284 ::SendMessage(GetHwndOf(btnOldDefault), BM_SETSTYLE, style, 1L);
285 }
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 }
292 }
293
294 // and set BS_DEFPUSHBUTTON for this button
295 wxButton *btnDefault = wxDynamicCast(winDefault, wxButton);
296 if ( btnDefault )
297 {
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 }
308 }
309 }
310
311 // ----------------------------------------------------------------------------
312 // helpers
313 // ----------------------------------------------------------------------------
314
315 bool wxButton::SendClickEvent()
316 {
317 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
318 event.SetEventObject(this);
319
320 return ProcessCommand(event);
321 }
322
323 void wxButton::Command(wxCommandEvent & event)
324 {
325 ProcessCommand(event);
326 }
327
328 // ----------------------------------------------------------------------------
329 // event/message handlers
330 // ----------------------------------------------------------------------------
331
332 bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
333 {
334 bool processed = FALSE;
335 switch ( param )
336 {
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
340 processed = SendClickEvent();
341 break;
342 }
343
344 return processed;
345 }
346
347 long wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
348 {
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
352 if ( nMsg == WM_SETFOCUS )
353 {
354 SetTmpDefault();
355
356 // let the default processing take place too
357 }
358 else if ( nMsg == WM_KILLFOCUS )
359 {
360 UnsetTmpDefault();
361 }
362 else if ( nMsg == WM_LBUTTONDBLCLK )
363 {
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
368 // and continue with processing the message normally as well
369 }
370
371 // let the base class do all real processing
372 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
373 }
374
375 // ----------------------------------------------------------------------------
376 // owner-drawn buttons support
377 // ----------------------------------------------------------------------------
378
379 #ifdef __WIN32__
380
381 // drawing helpers
382
383 static 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
398 static 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
407 void wxButton::MakeOwnerDrawn()
408 {
409 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
410 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
411 {
412 // make it so
413 style |= BS_OWNERDRAW;
414 SetWindowLong(GetHwnd(), GWL_STYLE, style);
415 }
416 }
417
418 bool 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
433 bool 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
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
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
465 BWHHHHHHHHHHHHHHGBB
466 BWH GBB
467 BWH GBB
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
479 BG GB
480 BGGGGGGGGGGGGGGGGGB
481 BBBBBBBBBBBBBBBBBBB
482 */
483
484 static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
485 bool selected, bool pushed)
486 {
487 RECT r;
488 CopyRect(&r, &rectBtn);
489
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));
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
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
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);
540 DeleteObject(hpenLightGr);
541 DeleteObject(hpenGrey);
542 DeleteObject(hpenBlack);
543 }
544
545 bool 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
590 if ( pushed )
591 {
592 // the label is shifted by 1 pixel to create "pushed" effect
593 OffsetRect(&rectBtn, 1, 1);
594 }
595
596 DrawButtonText(hdc, &rectBtn, GetLabel(),
597 state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT)
598 : colFg);
599
600 ::DeleteObject(hbrushBackground);
601
602 return TRUE;
603 }
604
605 #endif // __WIN32__
606
607 #endif // wxUSE_BUTTON
608