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