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