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