]> git.saurik.com Git - wxWidgets.git/blob - src/msw/button.cpp
applied (slightly modified) wxToggleButton patch from John Norris and Axel Schlueter
[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 #ifndef WX_PRECOMP
32 #include "wx/button.h"
33 #include "wx/brush.h"
34 #include "wx/panel.h"
35 #include "wx/bmpbuttn.h"
36 #include "wx/settings.h"
37 #include "wx/dcscreen.h"
38 #endif
39
40 #include "wx/msw/private.h"
41
42 // ----------------------------------------------------------------------------
43 // macros
44 // ----------------------------------------------------------------------------
45
46 IMPLEMENT_DYNAMIC_CLASS(wxButton, wxControl)
47
48 // this macro tries to adjust the default button height to a reasonable value
49 // using the char height as the base
50 #define BUTTON_HEIGHT_FROM_CHAR_HEIGHT(cy) (11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy)/10)
51
52 // ============================================================================
53 // implementation
54 // ============================================================================
55
56 // ----------------------------------------------------------------------------
57 // creation/destruction
58 // ----------------------------------------------------------------------------
59
60 bool wxButton::Create(wxWindow *parent,
61 wxWindowID id,
62 const wxString& label,
63 const wxPoint& pos,
64 const wxSize& size,
65 long style,
66 const wxValidator& validator,
67 const wxString& name)
68 {
69 if ( !CreateBase(parent, id, pos, size, style, validator, name) )
70 return FALSE;
71
72 parent->AddChild((wxButton *)this);
73
74 m_backgroundColour = parent->GetBackgroundColour();
75 m_foregroundColour = parent->GetForegroundColour();
76
77 long msStyle = WS_VISIBLE | WS_TABSTOP | WS_CHILD /* | WS_CLIPSIBLINGS */ ;
78
79 #ifdef __WIN32__
80 if(m_windowStyle & wxBU_LEFT)
81 msStyle |= BS_LEFT;
82 if(m_windowStyle & wxBU_RIGHT)
83 msStyle |= BS_RIGHT;
84 if(m_windowStyle & wxBU_TOP)
85 msStyle |= BS_TOP;
86 if(m_windowStyle & wxBU_BOTTOM)
87 msStyle |= BS_BOTTOM;
88 #endif
89
90 m_hWnd = (WXHWND)CreateWindowEx
91 (
92 MakeExtendedStyle(m_windowStyle),
93 wxT("BUTTON"),
94 label,
95 msStyle,
96 0, 0, 0, 0,
97 GetWinHwnd(parent),
98 (HMENU)m_windowId,
99 wxGetInstance(),
100 NULL
101 );
102
103 if (m_hWnd == 0)
104 {
105 wxString msg;
106 #ifdef __WIN16__
107 msg.Printf(wxT("CreateWindowEx failed"));
108 #else
109 msg.Printf(wxT("CreateWindowEx failed with error number %ld"), (long) GetLastError());
110 #endif
111 wxFAIL_MSG(msg);
112 }
113
114 // Subclass again for purposes of dialog editing mode
115 SubclassWin(m_hWnd);
116
117 SetFont(parent->GetFont());
118
119 SetSize(pos.x, pos.y, size.x, size.y);
120
121 return TRUE;
122 }
123
124 wxButton::~wxButton()
125 {
126 wxPanel *panel = wxDynamicCast(GetParent(), wxPanel);
127 if ( panel )
128 {
129 if ( panel->GetDefaultItem() == this )
130 {
131 // don't leave the panel with invalid default item
132 panel->SetDefaultItem(NULL);
133 }
134 }
135 }
136
137 // ----------------------------------------------------------------------------
138 // size management including autosizing
139 // ----------------------------------------------------------------------------
140
141 wxSize wxButton::DoGetBestSize() const
142 {
143 wxString label = wxGetWindowText(GetHWND());
144 int wBtn;
145 GetTextExtent(label, &wBtn, NULL);
146
147 int wChar, hChar;
148 wxGetCharSize(GetHWND(), &wChar, &hChar, &GetFont());
149
150 // add a margin - the button is wider than just its label
151 wBtn += 3*wChar;
152
153 // the button height is proportional to the height of the font used
154 int hBtn = BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hChar);
155
156 wxSize sz = GetDefaultSize();
157 if (wBtn > sz.x) sz.x = wBtn;
158 if (hBtn > sz.y) sz.y = hBtn;
159
160 return sz;
161 }
162
163 /* static */
164 wxSize wxButton::GetDefaultSize()
165 {
166 static wxSize s_sizeBtn;
167
168 if ( s_sizeBtn.x == 0 )
169 {
170 wxScreenDC dc;
171 dc.SetFont(wxSystemSettings::GetSystemFont(wxSYS_DEFAULT_GUI_FONT));
172
173 // the size of a standard button in the dialog units is 50x14,
174 // translate this to pixels
175 // NB1: the multipliers come from the Windows convention
176 // NB2: the extra +1/+2 were needed to get the size be the same as the
177 // size of the buttons in the standard dialog - I don't know how
178 // this happens, but on my system this size is 75x23 in pixels and
179 // 23*8 isn't even divisible by 14... Would be nice to understand
180 // why these constants are needed though!
181 s_sizeBtn.x = (50 * (dc.GetCharWidth() + 1))/4;
182 s_sizeBtn.y = ((14 * dc.GetCharHeight()) + 2)/8;
183 }
184
185 return s_sizeBtn;
186 }
187
188 // ----------------------------------------------------------------------------
189 // set this button as the default one in its panel
190 // ----------------------------------------------------------------------------
191
192 void wxButton::SetDefault()
193 {
194 wxWindow *parent = GetParent();
195 wxButton *btnOldDefault = NULL;
196 wxPanel *panel = wxDynamicCast(parent, wxPanel);
197 if ( panel )
198 {
199 btnOldDefault = panel->GetDefaultItem();
200 panel->SetDefaultItem(this);
201 }
202
203 if ( parent )
204 {
205 SendMessage(GetWinHwnd(parent), DM_SETDEFID, m_windowId, 0L);
206 }
207
208 if ( btnOldDefault )
209 {
210 // remove the BS_DEFPUSHBUTTON style from the other button
211 long style = GetWindowLong(GetHwndOf(btnOldDefault), GWL_STYLE);
212
213 // don't do it with the owner drawn buttons because it will reset
214 // BS_OWNERDRAW style bit too (BS_OWNERDRAW & BS_DEFPUSHBUTTON != 0)!
215 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
216 {
217 style &= ~BS_DEFPUSHBUTTON;
218 SendMessage(GetHwndOf(btnOldDefault), BM_SETSTYLE, style, 1L);
219 }
220 else
221 {
222 // redraw the button - it will notice itself that it's not the
223 // default one any longer
224 btnOldDefault->Refresh();
225 }
226 }
227
228 // set this button as the default
229 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
230 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
231 {
232 style |= BS_DEFPUSHBUTTON;
233 SendMessage(GetHwnd(), BM_SETSTYLE, style, 1L);
234 }
235 }
236
237 // ----------------------------------------------------------------------------
238 // helpers
239 // ----------------------------------------------------------------------------
240
241 bool wxButton::SendClickEvent()
242 {
243 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
244 event.SetEventObject(this);
245
246 return ProcessCommand(event);
247 }
248
249 void wxButton::Command(wxCommandEvent & event)
250 {
251 ProcessCommand(event);
252 }
253
254 // ----------------------------------------------------------------------------
255 // event/message handlers
256 // ----------------------------------------------------------------------------
257
258 bool wxButton::MSWCommand(WXUINT param, WXWORD id)
259 {
260 bool processed = FALSE;
261 switch ( param )
262 {
263 case 1: // message came from an accelerator
264 case BN_CLICKED: // normal buttons send this
265 case BN_DOUBLECLICKED: // owner-drawn ones also send this
266 processed = SendClickEvent();
267 break;
268 }
269
270 return processed;
271 }
272
273 long wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
274 {
275 // when we receive focus, we want to become the default button in our
276 // parent panel
277 if ( nMsg == WM_SETFOCUS )
278 {
279 SetDefault();
280
281 // let the default processign take place too
282 }
283 else if ( nMsg == WM_LBUTTONDBLCLK )
284 {
285 // emulate a click event to force an owner-drawn button to change its
286 // appearance - without this, it won't do it
287 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN, wParam, lParam);
288
289 // and conitnue with processing the message normally as well
290 }
291
292 // let the base class do all real processing
293 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
294 }
295
296 // ----------------------------------------------------------------------------
297 // owner-drawn buttons support
298 // ----------------------------------------------------------------------------
299
300 #ifdef __WIN32__
301
302 // drawing helpers
303
304 static void DrawButtonText(HDC hdc,
305 RECT *pRect,
306 const wxString& text,
307 COLORREF col)
308 {
309 COLORREF colOld = SetTextColor(hdc, col);
310 int modeOld = SetBkMode(hdc, TRANSPARENT);
311
312 DrawText(hdc, text, text.length(), pRect,
313 DT_CENTER | DT_VCENTER | DT_SINGLELINE);
314
315 SetBkMode(hdc, modeOld);
316 SetTextColor(hdc, colOld);
317 }
318
319 static void DrawRect(HDC hdc, const RECT& r)
320 {
321 MoveToEx(hdc, r.left, r.top, NULL);
322 LineTo(hdc, r.right, r.top);
323 LineTo(hdc, r.right, r.bottom);
324 LineTo(hdc, r.left, r.bottom);
325 LineTo(hdc, r.left, r.top);
326 }
327
328 void wxButton::MakeOwnerDrawn()
329 {
330 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
331 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
332 {
333 // make it so
334 style |= BS_OWNERDRAW;
335 SetWindowLong(GetHwnd(), GWL_STYLE, style);
336 }
337 }
338
339 bool wxButton::SetBackgroundColour(const wxColour &colour)
340 {
341 if ( !wxControl::SetBackgroundColour(colour) )
342 {
343 // nothing to do
344 return FALSE;
345 }
346
347 MakeOwnerDrawn();
348
349 Refresh();
350
351 return TRUE;
352 }
353
354 bool wxButton::SetForegroundColour(const wxColour &colour)
355 {
356 if ( !wxControl::SetForegroundColour(colour) )
357 {
358 // nothing to do
359 return FALSE;
360 }
361
362 MakeOwnerDrawn();
363
364 Refresh();
365
366 return TRUE;
367 }
368
369 /*
370 The button frame looks like this normally:
371
372 WWWWWWWWWWWWWWWWWWB
373 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
374 WH GB H = light grey (LIGHT)
375 WH GB G = dark grey (SHADOW)
376 WH GB B = black (DKSHADOW)
377 WH GB
378 WGGGGGGGGGGGGGGGGGB
379 BBBBBBBBBBBBBBBBBBB
380
381 When the button is selected, the button becomes like this (the total button
382 size doesn't change):
383
384 BBBBBBBBBBBBBBBBBBB
385 BWWWWWWWWWWWWWWWWBB
386 BWHHHHHHHHHHHHHHGBB
387 BWH GBB
388 BWH GBB
389 BWGGGGGGGGGGGGGGGBB
390 BBBBBBBBBBBBBBBBBBB
391 BBBBBBBBBBBBBBBBBBB
392
393 When the button is pushed (while selected) it is like:
394
395 BBBBBBBBBBBBBBBBBBB
396 BGGGGGGGGGGGGGGGGGB
397 BG GB
398 BG GB
399 BG GB
400 BG GB
401 BGGGGGGGGGGGGGGGGGB
402 BBBBBBBBBBBBBBBBBBB
403 */
404
405 static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
406 bool selected, bool pushed)
407 {
408 RECT r;
409 CopyRect(&r, &rectBtn);
410
411 HPEN hpenBlack = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW)),
412 hpenGrey = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW)),
413 hpenLightGr = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT)),
414 hpenWhite = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT));
415
416 HPEN hpenOld = (HPEN)SelectObject(hdc, hpenBlack);
417
418 r.right--;
419 r.bottom--;
420
421 if ( pushed )
422 {
423 DrawRect(hdc, r);
424
425 (void)SelectObject(hdc, hpenGrey);
426 InflateRect(&r, -1, -1);
427
428 DrawRect(hdc, r);
429 }
430 else // !pushed
431 {
432 if ( selected )
433 {
434 DrawRect(hdc, r);
435
436 InflateRect(&r, -1, -1);
437 }
438
439 MoveToEx(hdc, r.left, r.bottom, NULL);
440 LineTo(hdc, r.right, r.bottom);
441 LineTo(hdc, r.right, r.top - 1);
442
443 (void)SelectObject(hdc, hpenWhite);
444 MoveToEx(hdc, r.left, r.bottom - 1, NULL);
445 LineTo(hdc, r.left, r.top);
446 LineTo(hdc, r.right, r.top);
447
448 (void)SelectObject(hdc, hpenLightGr);
449 MoveToEx(hdc, r.left + 1, r.bottom - 2, NULL);
450 LineTo(hdc, r.left + 1, r.top + 1);
451 LineTo(hdc, r.right - 1, r.top + 1);
452
453 (void)SelectObject(hdc, hpenGrey);
454 MoveToEx(hdc, r.left + 1, r.bottom - 1, NULL);
455 LineTo(hdc, r.right - 1, r.bottom - 1);
456 LineTo(hdc, r.right - 1, r.top);
457 }
458
459 (void)SelectObject(hdc, hpenOld);
460 DeleteObject(hpenWhite);
461 DeleteObject(hpenLightGr);
462 DeleteObject(hpenGrey);
463 DeleteObject(hpenBlack);
464 }
465
466 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
467 {
468 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
469
470 RECT rectBtn;
471 CopyRect(&rectBtn, &lpDIS->rcItem);
472
473 COLORREF colBg = wxColourToRGB(GetBackgroundColour()),
474 colFg = wxColourToRGB(GetForegroundColour());
475
476 HDC hdc = lpDIS->hDC;
477 UINT state = lpDIS->itemState;
478
479 // first, draw the background
480 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
481
482 FillRect(hdc, &rectBtn, hbrushBackground);
483
484 // draw the border for the current state
485 bool selected = (state & ODS_SELECTED) != 0;
486 if ( !selected )
487 {
488 wxPanel *panel = wxDynamicCast(GetParent(), wxPanel);
489 if ( panel )
490 {
491 selected = panel->GetDefaultItem() == this;
492 }
493 }
494 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
495
496 DrawButtonFrame(hdc, rectBtn, selected, pushed);
497
498 // draw the focus rect if needed
499 if ( state & ODS_FOCUS )
500 {
501 RECT rectFocus;
502 CopyRect(&rectFocus, &rectBtn);
503
504 // I don't know where does this constant come from, but this is how
505 // Windows draws them
506 InflateRect(&rectFocus, -4, -4);
507
508 DrawFocusRect(hdc, &rectFocus);
509 }
510
511 if ( pushed )
512 {
513 // the label is shifted by 1 pixel to create "pushed" effect
514 OffsetRect(&rectBtn, 1, 1);
515 }
516
517 DrawButtonText(hdc, &rectBtn, GetLabel(),
518 state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT)
519 : colFg);
520
521 ::DeleteObject(hbrushBackground);
522
523 return TRUE;
524 }
525
526 #endif // __WIN32__