]> git.saurik.com Git - wxWidgets.git/blob - src/msw/button.cpp
1. always create the buttons with WS_CLIPSIBLINGS style, this prevetns them
[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 return MSWCreateControl(_T("BUTTON"), label, pos, size, style);
75 }
76
77 wxButton::~wxButton()
78 {
79 }
80
81 // ----------------------------------------------------------------------------
82 // flags
83 // ----------------------------------------------------------------------------
84
85 WXDWORD wxButton::MSWGetStyle(long style, WXDWORD *exstyle) const
86 {
87 // buttons never have an external border, they draw their own one
88 WXDWORD msStyle = wxControl::MSWGetStyle
89 (
90 (style & ~wxBORDER_MASK) | wxBORDER_NONE, exstyle
91 );
92
93 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
94 // each other in any resizeable dialog which has more than one button in
95 // the bottom
96 msStyle |= WS_CLIPSIBLINGS;
97
98 #ifdef __WIN32__
99 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
100 // and wxBU_RIGHT to get BS_CENTER!
101 if ( style & wxBU_LEFT )
102 msStyle |= BS_LEFT;
103 if ( style & wxBU_RIGHT )
104 msStyle |= BS_RIGHT;
105 if ( style & wxBU_TOP )
106 msStyle |= BS_TOP;
107 if ( style & wxBU_BOTTOM )
108 msStyle |= BS_BOTTOM;
109 #endif // __WIN32__
110
111 return msStyle;
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 wxButtonBase::GetDefaultSize()
142 {
143 static wxSize s_sizeBtn;
144
145 if ( s_sizeBtn.x == 0 )
146 {
147 wxScreenDC dc;
148 dc.SetFont(wxSystemSettings::GetFont(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;
173 if ( parent )
174 {
175 wxWindow *winOldDefault = parent->SetDefaultItem(this);
176 btnOldDefault = wxDynamicCast(winOldDefault, wxButton);
177
178 ::SendMessage(GetWinHwnd(parent), DM_SETDEFID, m_windowId, 0L);
179 }
180 else // is a button without parent really normal?
181 {
182 btnOldDefault = NULL;
183 }
184
185 if ( btnOldDefault && btnOldDefault != this )
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 WXUNUSED(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 // emulate a click event to force an owner-drawn button to change its
263 // appearance - without this, it won't do it
264 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN, wParam, lParam);
265
266 // and conitnue with processing the message normally as well
267 }
268 #if 0
269 else if ( nMsg == WM_MOVE )
270 {
271 Refresh();
272 }
273 #endif
274
275 // let the base class do all real processing
276 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
277 }
278
279 // ----------------------------------------------------------------------------
280 // owner-drawn buttons support
281 // ----------------------------------------------------------------------------
282
283 #ifdef __WIN32__
284
285 // drawing helpers
286
287 static void DrawButtonText(HDC hdc,
288 RECT *pRect,
289 const wxString& text,
290 COLORREF col)
291 {
292 COLORREF colOld = SetTextColor(hdc, col);
293 int modeOld = SetBkMode(hdc, TRANSPARENT);
294
295 DrawText(hdc, text, text.length(), pRect,
296 DT_CENTER | DT_VCENTER | DT_SINGLELINE);
297
298 SetBkMode(hdc, modeOld);
299 SetTextColor(hdc, colOld);
300 }
301
302 static void DrawRect(HDC hdc, const RECT& r)
303 {
304 MoveToEx(hdc, r.left, r.top, NULL);
305 LineTo(hdc, r.right, r.top);
306 LineTo(hdc, r.right, r.bottom);
307 LineTo(hdc, r.left, r.bottom);
308 LineTo(hdc, r.left, r.top);
309 }
310
311 void wxButton::MakeOwnerDrawn()
312 {
313 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
314 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
315 {
316 // make it so
317 style |= BS_OWNERDRAW;
318 SetWindowLong(GetHwnd(), GWL_STYLE, style);
319 }
320 }
321
322 bool wxButton::SetBackgroundColour(const wxColour &colour)
323 {
324 if ( !wxControl::SetBackgroundColour(colour) )
325 {
326 // nothing to do
327 return FALSE;
328 }
329
330 MakeOwnerDrawn();
331
332 Refresh();
333
334 return TRUE;
335 }
336
337 bool wxButton::SetForegroundColour(const wxColour &colour)
338 {
339 if ( !wxControl::SetForegroundColour(colour) )
340 {
341 // nothing to do
342 return FALSE;
343 }
344
345 MakeOwnerDrawn();
346
347 Refresh();
348
349 return TRUE;
350 }
351
352 /*
353 The button frame looks like this normally:
354
355 WWWWWWWWWWWWWWWWWWB
356 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
357 WH GB H = light grey (LIGHT)
358 WH GB G = dark grey (SHADOW)
359 WH GB B = black (DKSHADOW)
360 WH GB
361 WGGGGGGGGGGGGGGGGGB
362 BBBBBBBBBBBBBBBBBBB
363
364 When the button is selected, the button becomes like this (the total button
365 size doesn't change):
366
367 BBBBBBBBBBBBBBBBBBB
368 BWWWWWWWWWWWWWWWWBB
369 BWHHHHHHHHHHHHHHGBB
370 BWH GBB
371 BWH GBB
372 BWGGGGGGGGGGGGGGGBB
373 BBBBBBBBBBBBBBBBBBB
374 BBBBBBBBBBBBBBBBBBB
375
376 When the button is pushed (while selected) it is like:
377
378 BBBBBBBBBBBBBBBBBBB
379 BGGGGGGGGGGGGGGGGGB
380 BG GB
381 BG GB
382 BG GB
383 BG GB
384 BGGGGGGGGGGGGGGGGGB
385 BBBBBBBBBBBBBBBBBBB
386 */
387
388 static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
389 bool selected, bool pushed)
390 {
391 RECT r;
392 CopyRect(&r, &rectBtn);
393
394 HPEN hpenBlack = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW)),
395 hpenGrey = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW)),
396 hpenLightGr = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT)),
397 hpenWhite = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT));
398
399 HPEN hpenOld = (HPEN)SelectObject(hdc, hpenBlack);
400
401 r.right--;
402 r.bottom--;
403
404 if ( pushed )
405 {
406 DrawRect(hdc, r);
407
408 (void)SelectObject(hdc, hpenGrey);
409 InflateRect(&r, -1, -1);
410
411 DrawRect(hdc, r);
412 }
413 else // !pushed
414 {
415 if ( selected )
416 {
417 DrawRect(hdc, r);
418
419 InflateRect(&r, -1, -1);
420 }
421
422 MoveToEx(hdc, r.left, r.bottom, NULL);
423 LineTo(hdc, r.right, r.bottom);
424 LineTo(hdc, r.right, r.top - 1);
425
426 (void)SelectObject(hdc, hpenWhite);
427 MoveToEx(hdc, r.left, r.bottom - 1, NULL);
428 LineTo(hdc, r.left, r.top);
429 LineTo(hdc, r.right, r.top);
430
431 (void)SelectObject(hdc, hpenLightGr);
432 MoveToEx(hdc, r.left + 1, r.bottom - 2, NULL);
433 LineTo(hdc, r.left + 1, r.top + 1);
434 LineTo(hdc, r.right - 1, r.top + 1);
435
436 (void)SelectObject(hdc, hpenGrey);
437 MoveToEx(hdc, r.left + 1, r.bottom - 1, NULL);
438 LineTo(hdc, r.right - 1, r.bottom - 1);
439 LineTo(hdc, r.right - 1, r.top);
440 }
441
442 (void)SelectObject(hdc, hpenOld);
443 DeleteObject(hpenWhite);
444 DeleteObject(hpenLightGr);
445 DeleteObject(hpenGrey);
446 DeleteObject(hpenBlack);
447 }
448
449 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
450 {
451 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
452
453 RECT rectBtn;
454 CopyRect(&rectBtn, &lpDIS->rcItem);
455
456 COLORREF colBg = wxColourToRGB(GetBackgroundColour()),
457 colFg = wxColourToRGB(GetForegroundColour());
458
459 HDC hdc = lpDIS->hDC;
460 UINT state = lpDIS->itemState;
461
462 // first, draw the background
463 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
464
465 FillRect(hdc, &rectBtn, hbrushBackground);
466
467 // draw the border for the current state
468 bool selected = (state & ODS_SELECTED) != 0;
469 if ( !selected )
470 {
471 wxPanel *panel = wxDynamicCast(GetParent(), wxPanel);
472 if ( panel )
473 {
474 selected = panel->GetDefaultItem() == this;
475 }
476 }
477 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
478
479 DrawButtonFrame(hdc, rectBtn, selected, pushed);
480
481 // draw the focus rect if needed
482 if ( state & ODS_FOCUS )
483 {
484 RECT rectFocus;
485 CopyRect(&rectFocus, &rectBtn);
486
487 // I don't know where does this constant come from, but this is how
488 // Windows draws them
489 InflateRect(&rectFocus, -4, -4);
490
491 DrawFocusRect(hdc, &rectFocus);
492 }
493
494 if ( pushed )
495 {
496 // the label is shifted by 1 pixel to create "pushed" effect
497 OffsetRect(&rectBtn, 1, 1);
498 }
499
500 DrawButtonText(hdc, &rectBtn, GetLabel(),
501 state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT)
502 : colFg);
503
504 ::DeleteObject(hbrushBackground);
505
506 return TRUE;
507 }
508
509 #endif // __WIN32__
510
511 #endif // wxUSE_BUTTON
512