]> git.saurik.com Git - wxWidgets.git/blob - src/msw/button.cpp
Glitches in wxDelegateRenderer
[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 // set this button as the default one in its panel
184 // ----------------------------------------------------------------------------
185
186 void wxButton::SetDefault()
187 {
188 wxWindow *parent = GetParent();
189 wxButton *btnOldDefault;
190 if ( parent )
191 {
192 wxWindow *winOldDefault = parent->SetDefaultItem(this);
193 btnOldDefault = wxDynamicCast(winOldDefault, wxButton);
194
195 ::SendMessage(GetWinHwnd(parent), DM_SETDEFID, m_windowId, 0L);
196 }
197 else // is a button without parent really normal?
198 {
199 btnOldDefault = NULL;
200 }
201
202 if ( btnOldDefault && btnOldDefault != this )
203 {
204 // remove the BS_DEFPUSHBUTTON style from the other button
205 long style = GetWindowLong(GetHwndOf(btnOldDefault), GWL_STYLE);
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)!
209 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
210 {
211 style &= ~BS_DEFPUSHBUTTON;
212 SendMessage(GetHwndOf(btnOldDefault), BM_SETSTYLE, style, 1L);
213 }
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 }
220 }
221
222 // set this button as the default
223 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
224 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
225 {
226 style |= BS_DEFPUSHBUTTON;
227 SendMessage(GetHwnd(), BM_SETSTYLE, style, 1L);
228 }
229 }
230
231 // ----------------------------------------------------------------------------
232 // helpers
233 // ----------------------------------------------------------------------------
234
235 bool wxButton::SendClickEvent()
236 {
237 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
238 event.SetEventObject(this);
239
240 return ProcessCommand(event);
241 }
242
243 void wxButton::Command(wxCommandEvent & event)
244 {
245 ProcessCommand(event);
246 }
247
248 // ----------------------------------------------------------------------------
249 // event/message handlers
250 // ----------------------------------------------------------------------------
251
252 bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
253 {
254 bool processed = FALSE;
255 switch ( param )
256 {
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
260 processed = SendClickEvent();
261 break;
262 }
263
264 return processed;
265 }
266
267 long wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
268 {
269 // when we receive focus, we want to become the default button in our
270 // parent panel
271 if ( nMsg == WM_SETFOCUS )
272 {
273 SetDefault();
274
275 // let the default processign take place too
276 }
277 else if ( nMsg == WM_LBUTTONDBLCLK )
278 {
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
284 }
285
286 // let the base class do all real processing
287 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
288 }
289
290 // ----------------------------------------------------------------------------
291 // owner-drawn buttons support
292 // ----------------------------------------------------------------------------
293
294 #ifdef __WIN32__
295
296 // drawing helpers
297
298 static 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
313 static 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
322 void wxButton::MakeOwnerDrawn()
323 {
324 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
325 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
326 {
327 // make it so
328 style |= BS_OWNERDRAW;
329 SetWindowLong(GetHwnd(), GWL_STYLE, style);
330 }
331 }
332
333 bool 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
348 bool 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
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
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
380 BWHHHHHHHHHHHHHHGBB
381 BWH GBB
382 BWH GBB
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
394 BG GB
395 BGGGGGGGGGGGGGGGGGB
396 BBBBBBBBBBBBBBBBBBB
397 */
398
399 static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
400 bool selected, bool pushed)
401 {
402 RECT r;
403 CopyRect(&r, &rectBtn);
404
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));
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
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
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);
455 DeleteObject(hpenLightGr);
456 DeleteObject(hpenGrey);
457 DeleteObject(hpenBlack);
458 }
459
460 bool 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
505 if ( pushed )
506 {
507 // the label is shifted by 1 pixel to create "pushed" effect
508 OffsetRect(&rectBtn, 1, 1);
509 }
510
511 DrawButtonText(hdc, &rectBtn, GetLabel(),
512 state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT)
513 : colFg);
514
515 ::DeleteObject(hbrushBackground);
516
517 return TRUE;
518 }
519
520 #endif // __WIN32__
521
522 #endif // wxUSE_BUTTON
523