]> git.saurik.com Git - wxWidgets.git/blob - src/msw/button.cpp
implemented menu drawing in the GTK theme
[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
269 // let the base class do all real processing
270 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
271 }
272
273 // ----------------------------------------------------------------------------
274 // owner-drawn buttons support
275 // ----------------------------------------------------------------------------
276
277 #ifdef __WIN32__
278
279 // drawing helpers
280
281 static void DrawButtonText(HDC hdc,
282 RECT *pRect,
283 const wxString& text,
284 COLORREF col)
285 {
286 COLORREF colOld = SetTextColor(hdc, col);
287 int modeOld = SetBkMode(hdc, TRANSPARENT);
288
289 DrawText(hdc, text, text.length(), pRect,
290 DT_CENTER | DT_VCENTER | DT_SINGLELINE);
291
292 SetBkMode(hdc, modeOld);
293 SetTextColor(hdc, colOld);
294 }
295
296 static void DrawRect(HDC hdc, const RECT& r)
297 {
298 MoveToEx(hdc, r.left, r.top, NULL);
299 LineTo(hdc, r.right, r.top);
300 LineTo(hdc, r.right, r.bottom);
301 LineTo(hdc, r.left, r.bottom);
302 LineTo(hdc, r.left, r.top);
303 }
304
305 void wxButton::MakeOwnerDrawn()
306 {
307 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
308 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
309 {
310 // make it so
311 style |= BS_OWNERDRAW;
312 SetWindowLong(GetHwnd(), GWL_STYLE, style);
313 }
314 }
315
316 bool wxButton::SetBackgroundColour(const wxColour &colour)
317 {
318 if ( !wxControl::SetBackgroundColour(colour) )
319 {
320 // nothing to do
321 return FALSE;
322 }
323
324 MakeOwnerDrawn();
325
326 Refresh();
327
328 return TRUE;
329 }
330
331 bool wxButton::SetForegroundColour(const wxColour &colour)
332 {
333 if ( !wxControl::SetForegroundColour(colour) )
334 {
335 // nothing to do
336 return FALSE;
337 }
338
339 MakeOwnerDrawn();
340
341 Refresh();
342
343 return TRUE;
344 }
345
346 /*
347 The button frame looks like this normally:
348
349 WWWWWWWWWWWWWWWWWWB
350 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
351 WH GB H = light grey (LIGHT)
352 WH GB G = dark grey (SHADOW)
353 WH GB B = black (DKSHADOW)
354 WH GB
355 WGGGGGGGGGGGGGGGGGB
356 BBBBBBBBBBBBBBBBBBB
357
358 When the button is selected, the button becomes like this (the total button
359 size doesn't change):
360
361 BBBBBBBBBBBBBBBBBBB
362 BWWWWWWWWWWWWWWWWBB
363 BWHHHHHHHHHHHHHHGBB
364 BWH GBB
365 BWH GBB
366 BWGGGGGGGGGGGGGGGBB
367 BBBBBBBBBBBBBBBBBBB
368 BBBBBBBBBBBBBBBBBBB
369
370 When the button is pushed (while selected) it is like:
371
372 BBBBBBBBBBBBBBBBBBB
373 BGGGGGGGGGGGGGGGGGB
374 BG GB
375 BG GB
376 BG GB
377 BG GB
378 BGGGGGGGGGGGGGGGGGB
379 BBBBBBBBBBBBBBBBBBB
380 */
381
382 static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
383 bool selected, bool pushed)
384 {
385 RECT r;
386 CopyRect(&r, &rectBtn);
387
388 HPEN hpenBlack = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW)),
389 hpenGrey = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW)),
390 hpenLightGr = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT)),
391 hpenWhite = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT));
392
393 HPEN hpenOld = (HPEN)SelectObject(hdc, hpenBlack);
394
395 r.right--;
396 r.bottom--;
397
398 if ( pushed )
399 {
400 DrawRect(hdc, r);
401
402 (void)SelectObject(hdc, hpenGrey);
403 InflateRect(&r, -1, -1);
404
405 DrawRect(hdc, r);
406 }
407 else // !pushed
408 {
409 if ( selected )
410 {
411 DrawRect(hdc, r);
412
413 InflateRect(&r, -1, -1);
414 }
415
416 MoveToEx(hdc, r.left, r.bottom, NULL);
417 LineTo(hdc, r.right, r.bottom);
418 LineTo(hdc, r.right, r.top - 1);
419
420 (void)SelectObject(hdc, hpenWhite);
421 MoveToEx(hdc, r.left, r.bottom - 1, NULL);
422 LineTo(hdc, r.left, r.top);
423 LineTo(hdc, r.right, r.top);
424
425 (void)SelectObject(hdc, hpenLightGr);
426 MoveToEx(hdc, r.left + 1, r.bottom - 2, NULL);
427 LineTo(hdc, r.left + 1, r.top + 1);
428 LineTo(hdc, r.right - 1, r.top + 1);
429
430 (void)SelectObject(hdc, hpenGrey);
431 MoveToEx(hdc, r.left + 1, r.bottom - 1, NULL);
432 LineTo(hdc, r.right - 1, r.bottom - 1);
433 LineTo(hdc, r.right - 1, r.top);
434 }
435
436 (void)SelectObject(hdc, hpenOld);
437 DeleteObject(hpenWhite);
438 DeleteObject(hpenLightGr);
439 DeleteObject(hpenGrey);
440 DeleteObject(hpenBlack);
441 }
442
443 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
444 {
445 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
446
447 RECT rectBtn;
448 CopyRect(&rectBtn, &lpDIS->rcItem);
449
450 COLORREF colBg = wxColourToRGB(GetBackgroundColour()),
451 colFg = wxColourToRGB(GetForegroundColour());
452
453 HDC hdc = lpDIS->hDC;
454 UINT state = lpDIS->itemState;
455
456 // first, draw the background
457 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
458
459 FillRect(hdc, &rectBtn, hbrushBackground);
460
461 // draw the border for the current state
462 bool selected = (state & ODS_SELECTED) != 0;
463 if ( !selected )
464 {
465 wxPanel *panel = wxDynamicCast(GetParent(), wxPanel);
466 if ( panel )
467 {
468 selected = panel->GetDefaultItem() == this;
469 }
470 }
471 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
472
473 DrawButtonFrame(hdc, rectBtn, selected, pushed);
474
475 // draw the focus rect if needed
476 if ( state & ODS_FOCUS )
477 {
478 RECT rectFocus;
479 CopyRect(&rectFocus, &rectBtn);
480
481 // I don't know where does this constant come from, but this is how
482 // Windows draws them
483 InflateRect(&rectFocus, -4, -4);
484
485 DrawFocusRect(hdc, &rectFocus);
486 }
487
488 if ( pushed )
489 {
490 // the label is shifted by 1 pixel to create "pushed" effect
491 OffsetRect(&rectBtn, 1, 1);
492 }
493
494 DrawButtonText(hdc, &rectBtn, GetLabel(),
495 state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT)
496 : colFg);
497
498 ::DeleteObject(hbrushBackground);
499
500 return TRUE;
501 }
502
503 #endif // __WIN32__
504
505 #endif // wxUSE_BUTTON
506