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