]> git.saurik.com Git - wxWidgets.git/blob - src/msw/button.cpp
c00c3d11d04b10a6a517c367a76bb9c2f3f303d1
[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 // set this button as the (permanently) default one in its panel
187 void wxButton::SetDefault()
188 {
189 wxWindow *parent = GetParent();
190
191 wxCHECK_RET( parent, _T("button without parent?") );
192
193 // set this one as the default button both for wxWindows and Windows
194 wxWindow *winOldDefault = parent->SetDefaultItem(this);
195 ::SendMessage(GetWinHwnd(parent), DM_SETDEFID, m_windowId, 0L);
196
197 UpdateDefaultStyle(this, winOldDefault);
198 }
199
200 void wxButton::SetTmpDefault()
201 {
202 wxWindow *parent = GetParent();
203
204 wxCHECK_RET( parent, _T("button without parent?") );
205
206 wxWindow *winOldDefault = parent->GetDefaultItem();
207 parent->SetTmpDefaultItem(this);
208 if ( winOldDefault != this )
209 {
210 UpdateDefaultStyle(this, winOldDefault);
211 }
212 //else: no styles to update
213 }
214
215 void wxButton::UnsetTmpDefault()
216 {
217 wxWindow *parent = GetParent();
218
219 wxCHECK_RET( parent, _T("button without parent?") );
220
221 parent->SetTmpDefaultItem(NULL);
222
223 wxWindow *winOldDefault = parent->GetDefaultItem();
224 if ( winOldDefault != this )
225 {
226 UpdateDefaultStyle(winOldDefault, this);
227 }
228 //else: we had been default before anyhow
229 }
230
231 /* static */
232 void
233 wxButton::UpdateDefaultStyle(wxWindow *winDefault, wxWindow *winOldDefault)
234 {
235 // clear the BS_DEFPUSHBUTTON for the old default button
236 wxButton *btnOldDefault = wxDynamicCast(winOldDefault, wxButton);
237 if ( btnOldDefault && btnOldDefault != winDefault )
238 {
239 // remove the BS_DEFPUSHBUTTON style from the other button
240 long style = ::GetWindowLong(GetHwndOf(btnOldDefault), GWL_STYLE);
241
242 // don't do it with the owner drawn buttons because it will reset
243 // BS_OWNERDRAW style bit too (BS_OWNERDRAW & BS_DEFPUSHBUTTON != 0)!
244 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
245 {
246 style &= ~BS_DEFPUSHBUTTON;
247 ::SendMessage(GetHwndOf(btnOldDefault), BM_SETSTYLE, style, 1L);
248 }
249 else
250 {
251 // redraw the button - it will notice itself that it's not the
252 // default one any longer
253 btnOldDefault->Refresh();
254 }
255 }
256
257 // and set BS_DEFPUSHBUTTON for this button
258 wxButton *btnDefault = wxDynamicCast(winDefault, wxButton);
259 if ( btnDefault )
260 {
261 long style = ::GetWindowLong(GetHwndOf(btnDefault), GWL_STYLE);
262 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
263 {
264 style |= BS_DEFPUSHBUTTON;
265 ::SendMessage(GetHwndOf(btnDefault), BM_SETSTYLE, style, 1L);
266 }
267 else
268 {
269 btnDefault->Refresh();
270 }
271 }
272 }
273
274 // ----------------------------------------------------------------------------
275 // helpers
276 // ----------------------------------------------------------------------------
277
278 bool wxButton::SendClickEvent()
279 {
280 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
281 event.SetEventObject(this);
282
283 return ProcessCommand(event);
284 }
285
286 void wxButton::Command(wxCommandEvent & event)
287 {
288 ProcessCommand(event);
289 }
290
291 // ----------------------------------------------------------------------------
292 // event/message handlers
293 // ----------------------------------------------------------------------------
294
295 bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
296 {
297 bool processed = FALSE;
298 switch ( param )
299 {
300 case 1: // message came from an accelerator
301 case BN_CLICKED: // normal buttons send this
302 case BN_DOUBLECLICKED: // owner-drawn ones also send this
303 processed = SendClickEvent();
304 break;
305 }
306
307 return processed;
308 }
309
310 long wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
311 {
312 // when we receive focus, we want to temporary become the default button in
313 // our parent panel so that pressing "Enter" would activate us -- and when
314 // losing it we should restore the previous default button as well
315 if ( nMsg == WM_SETFOCUS )
316 {
317 SetTmpDefault();
318
319 // let the default processing take place too
320 }
321 else if ( nMsg == WM_KILLFOCUS )
322 {
323 UnsetTmpDefault();
324 }
325 else if ( nMsg == WM_LBUTTONDBLCLK )
326 {
327 // emulate a click event to force an owner-drawn button to change its
328 // appearance - without this, it won't do it
329 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN, wParam, lParam);
330
331 // and continue with processing the message normally as well
332 }
333
334 // let the base class do all real processing
335 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
336 }
337
338 // ----------------------------------------------------------------------------
339 // owner-drawn buttons support
340 // ----------------------------------------------------------------------------
341
342 #ifdef __WIN32__
343
344 // drawing helpers
345
346 static void DrawButtonText(HDC hdc,
347 RECT *pRect,
348 const wxString& text,
349 COLORREF col)
350 {
351 COLORREF colOld = SetTextColor(hdc, col);
352 int modeOld = SetBkMode(hdc, TRANSPARENT);
353
354 DrawText(hdc, text, text.length(), pRect,
355 DT_CENTER | DT_VCENTER | DT_SINGLELINE);
356
357 SetBkMode(hdc, modeOld);
358 SetTextColor(hdc, colOld);
359 }
360
361 static void DrawRect(HDC hdc, const RECT& r)
362 {
363 MoveToEx(hdc, r.left, r.top, NULL);
364 LineTo(hdc, r.right, r.top);
365 LineTo(hdc, r.right, r.bottom);
366 LineTo(hdc, r.left, r.bottom);
367 LineTo(hdc, r.left, r.top);
368 }
369
370 void wxButton::MakeOwnerDrawn()
371 {
372 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
373 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
374 {
375 // make it so
376 style |= BS_OWNERDRAW;
377 SetWindowLong(GetHwnd(), GWL_STYLE, style);
378 }
379 }
380
381 bool wxButton::SetBackgroundColour(const wxColour &colour)
382 {
383 if ( !wxControl::SetBackgroundColour(colour) )
384 {
385 // nothing to do
386 return FALSE;
387 }
388
389 MakeOwnerDrawn();
390
391 Refresh();
392
393 return TRUE;
394 }
395
396 bool wxButton::SetForegroundColour(const wxColour &colour)
397 {
398 if ( !wxControl::SetForegroundColour(colour) )
399 {
400 // nothing to do
401 return FALSE;
402 }
403
404 MakeOwnerDrawn();
405
406 Refresh();
407
408 return TRUE;
409 }
410
411 /*
412 The button frame looks like this normally:
413
414 WWWWWWWWWWWWWWWWWWB
415 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
416 WH GB H = light grey (LIGHT)
417 WH GB G = dark grey (SHADOW)
418 WH GB B = black (DKSHADOW)
419 WH GB
420 WGGGGGGGGGGGGGGGGGB
421 BBBBBBBBBBBBBBBBBBB
422
423 When the button is selected, the button becomes like this (the total button
424 size doesn't change):
425
426 BBBBBBBBBBBBBBBBBBB
427 BWWWWWWWWWWWWWWWWBB
428 BWHHHHHHHHHHHHHHGBB
429 BWH GBB
430 BWH GBB
431 BWGGGGGGGGGGGGGGGBB
432 BBBBBBBBBBBBBBBBBBB
433 BBBBBBBBBBBBBBBBBBB
434
435 When the button is pushed (while selected) it is like:
436
437 BBBBBBBBBBBBBBBBBBB
438 BGGGGGGGGGGGGGGGGGB
439 BG GB
440 BG GB
441 BG GB
442 BG GB
443 BGGGGGGGGGGGGGGGGGB
444 BBBBBBBBBBBBBBBBBBB
445 */
446
447 static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
448 bool selected, bool pushed)
449 {
450 RECT r;
451 CopyRect(&r, &rectBtn);
452
453 HPEN hpenBlack = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW)),
454 hpenGrey = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW)),
455 hpenLightGr = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT)),
456 hpenWhite = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT));
457
458 HPEN hpenOld = (HPEN)SelectObject(hdc, hpenBlack);
459
460 r.right--;
461 r.bottom--;
462
463 if ( pushed )
464 {
465 DrawRect(hdc, r);
466
467 (void)SelectObject(hdc, hpenGrey);
468 InflateRect(&r, -1, -1);
469
470 DrawRect(hdc, r);
471 }
472 else // !pushed
473 {
474 if ( selected )
475 {
476 DrawRect(hdc, r);
477
478 InflateRect(&r, -1, -1);
479 }
480
481 MoveToEx(hdc, r.left, r.bottom, NULL);
482 LineTo(hdc, r.right, r.bottom);
483 LineTo(hdc, r.right, r.top - 1);
484
485 (void)SelectObject(hdc, hpenWhite);
486 MoveToEx(hdc, r.left, r.bottom - 1, NULL);
487 LineTo(hdc, r.left, r.top);
488 LineTo(hdc, r.right, r.top);
489
490 (void)SelectObject(hdc, hpenLightGr);
491 MoveToEx(hdc, r.left + 1, r.bottom - 2, NULL);
492 LineTo(hdc, r.left + 1, r.top + 1);
493 LineTo(hdc, r.right - 1, r.top + 1);
494
495 (void)SelectObject(hdc, hpenGrey);
496 MoveToEx(hdc, r.left + 1, r.bottom - 1, NULL);
497 LineTo(hdc, r.right - 1, r.bottom - 1);
498 LineTo(hdc, r.right - 1, r.top);
499 }
500
501 (void)SelectObject(hdc, hpenOld);
502 DeleteObject(hpenWhite);
503 DeleteObject(hpenLightGr);
504 DeleteObject(hpenGrey);
505 DeleteObject(hpenBlack);
506 }
507
508 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
509 {
510 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
511
512 RECT rectBtn;
513 CopyRect(&rectBtn, &lpDIS->rcItem);
514
515 COLORREF colBg = wxColourToRGB(GetBackgroundColour()),
516 colFg = wxColourToRGB(GetForegroundColour());
517
518 HDC hdc = lpDIS->hDC;
519 UINT state = lpDIS->itemState;
520
521 // first, draw the background
522 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
523
524 FillRect(hdc, &rectBtn, hbrushBackground);
525
526 // draw the border for the current state
527 bool selected = (state & ODS_SELECTED) != 0;
528 if ( !selected )
529 {
530 wxPanel *panel = wxDynamicCast(GetParent(), wxPanel);
531 if ( panel )
532 {
533 selected = panel->GetDefaultItem() == this;
534 }
535 }
536 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
537
538 DrawButtonFrame(hdc, rectBtn, selected, pushed);
539
540 // draw the focus rect if needed
541 if ( state & ODS_FOCUS )
542 {
543 RECT rectFocus;
544 CopyRect(&rectFocus, &rectBtn);
545
546 // I don't know where does this constant come from, but this is how
547 // Windows draws them
548 InflateRect(&rectFocus, -4, -4);
549
550 DrawFocusRect(hdc, &rectFocus);
551 }
552
553 if ( pushed )
554 {
555 // the label is shifted by 1 pixel to create "pushed" effect
556 OffsetRect(&rectBtn, 1, 1);
557 }
558
559 DrawButtonText(hdc, &rectBtn, GetLabel(),
560 state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT)
561 : colFg);
562
563 ::DeleteObject(hbrushBackground);
564
565 return TRUE;
566 }
567
568 #endif // __WIN32__
569
570 #endif // wxUSE_BUTTON
571