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