]> git.saurik.com Git - wxWidgets.git/blame - src/msw/button.cpp
yet another attempt to wxFrame's last focus bug
[wxWidgets.git] / src / msw / button.cpp
CommitLineData
2bda0e17 1/////////////////////////////////////////////////////////////////////////////
cd0b1709 2// Name: msw/button.cpp
2bda0e17
KB
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
edccf428 9// Licence: wxWindows license
2bda0e17
KB
10/////////////////////////////////////////////////////////////////////////////
11
edccf428
VZ
12// ============================================================================
13// declarations
14// ============================================================================
15
16// ----------------------------------------------------------------------------
17// headers
18// ----------------------------------------------------------------------------
cd0b1709 19
2bda0e17 20#ifdef __GNUG__
edccf428 21 #pragma implementation "button.h"
2bda0e17
KB
22#endif
23
24// For compilers that support precompilation, includes "wx.h".
25#include "wx/wxprec.h"
26
27#ifdef __BORLANDC__
edccf428 28 #pragma hdrstop
2bda0e17
KB
29#endif
30
31#ifndef WX_PRECOMP
edccf428
VZ
32 #include "wx/button.h"
33 #include "wx/brush.h"
4e938f5b 34 #include "wx/panel.h"
8a4df159 35 #include "wx/bmpbuttn.h"
fb39c7ec
RR
36 #include "wx/settings.h"
37 #include "wx/dcscreen.h"
2bda0e17
KB
38#endif
39
40#include "wx/msw/private.h"
41
edccf428
VZ
42// ----------------------------------------------------------------------------
43// macros
44// ----------------------------------------------------------------------------
45
cd0b1709 46IMPLEMENT_DYNAMIC_CLASS(wxButton, wxControl)
2bda0e17 47
edccf428
VZ
48// this macro tries to adjust the default button height to a reasonable value
49// using the char height as the base
1c4a764c 50#define BUTTON_HEIGHT_FROM_CHAR_HEIGHT(cy) (11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy)/10)
2bda0e17 51
edccf428
VZ
52// ============================================================================
53// implementation
54// ============================================================================
55
56// ----------------------------------------------------------------------------
57// creation/destruction
58// ----------------------------------------------------------------------------
59
60bool 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)
2bda0e17 68{
8d99be5f 69 if ( !CreateBase(parent, id, pos, size, style, validator, name) )
edccf428
VZ
70 return FALSE;
71
edccf428
VZ
72 parent->AddChild((wxButton *)this);
73
cd0b1709
VZ
74 m_backgroundColour = parent->GetBackgroundColour();
75 m_foregroundColour = parent->GetForegroundColour();
76
f6bcfd97
BP
77 long msStyle = WS_VISIBLE | WS_TABSTOP | WS_CHILD /* | WS_CLIPSIBLINGS */ ;
78
b0766406
JS
79 if ( m_windowStyle & wxCLIP_SIBLINGS )
80 msStyle |= WS_CLIPSIBLINGS;
81
f6bcfd97
BP
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
edccf428
VZ
92
93 m_hWnd = (WXHWND)CreateWindowEx
94 (
95 MakeExtendedStyle(m_windowStyle),
223d09f6 96 wxT("BUTTON"),
edccf428 97 label,
f6bcfd97 98 msStyle,
a77aa9d6 99 0, 0, 0, 0,
edccf428
VZ
100 GetWinHwnd(parent),
101 (HMENU)m_windowId,
102 wxGetInstance(),
103 NULL
104 );
105
f6bcfd97
BP
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
edccf428
VZ
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
2bda0e17 124 return TRUE;
2bda0e17
KB
125}
126
edccf428 127wxButton::~wxButton()
2bda0e17 128{
edccf428
VZ
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 }
2bda0e17
KB
138}
139
edccf428
VZ
140// ----------------------------------------------------------------------------
141// size management including autosizing
142// ----------------------------------------------------------------------------
143
f68586e5 144wxSize wxButton::DoGetBestSize() const
2bda0e17 145{
4438caf4
VZ
146 wxString label = wxGetWindowText(GetHWND());
147 int wBtn;
148 GetTextExtent(label, &wBtn, NULL);
edccf428 149
4438caf4
VZ
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);
edccf428 158
a63f2bec
VS
159 wxSize sz = GetDefaultSize();
160 if (wBtn > sz.x) sz.x = wBtn;
161 if (hBtn > sz.y) sz.y = hBtn;
162
163 return sz;
2bda0e17
KB
164}
165
e1f36ff8
VZ
166/* static */
167wxSize wxButton::GetDefaultSize()
168{
8c3c31d4 169 static wxSize s_sizeBtn;
e1f36ff8 170
8c3c31d4
VZ
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 }
e1f36ff8 187
8c3c31d4 188 return s_sizeBtn;
e1f36ff8
VZ
189}
190
4438caf4
VZ
191// ----------------------------------------------------------------------------
192// set this button as the default one in its panel
193// ----------------------------------------------------------------------------
194
edccf428 195void wxButton::SetDefault()
2bda0e17 196{
edccf428 197 wxWindow *parent = GetParent();
5d1d2d46 198 wxButton *btnOldDefault = NULL;
edccf428
VZ
199 wxPanel *panel = wxDynamicCast(parent, wxPanel);
200 if ( panel )
5d1d2d46
VZ
201 {
202 btnOldDefault = panel->GetDefaultItem();
edccf428 203 panel->SetDefaultItem(this);
5d1d2d46 204 }
2bda0e17 205
edccf428
VZ
206 if ( parent )
207 {
208 SendMessage(GetWinHwnd(parent), DM_SETDEFID, m_windowId, 0L);
209 }
8ed57d93 210
cd0b1709 211 if ( btnOldDefault )
5d1d2d46
VZ
212 {
213 // remove the BS_DEFPUSHBUTTON style from the other button
214 long style = GetWindowLong(GetHwndOf(btnOldDefault), GWL_STYLE);
cd0b1709
VZ
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)!
be4017f8 218 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
cd0b1709
VZ
219 {
220 style &= ~BS_DEFPUSHBUTTON;
221 SendMessage(GetHwndOf(btnOldDefault), BM_SETSTYLE, style, 1L);
222 }
be4017f8
VZ
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 }
5d1d2d46
VZ
229 }
230
231 // set this button as the default
232 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
be4017f8
VZ
233 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
234 {
235 style |= BS_DEFPUSHBUTTON;
236 SendMessage(GetHwnd(), BM_SETSTYLE, style, 1L);
237 }
2bda0e17
KB
238}
239
edccf428
VZ
240// ----------------------------------------------------------------------------
241// helpers
242// ----------------------------------------------------------------------------
243
244bool wxButton::SendClickEvent()
2bda0e17 245{
edccf428
VZ
246 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
247 event.SetEventObject(this);
248
249 return ProcessCommand(event);
2bda0e17
KB
250}
251
edccf428 252void wxButton::Command(wxCommandEvent & event)
2bda0e17 253{
edccf428 254 ProcessCommand(event);
2bda0e17
KB
255}
256
edccf428
VZ
257// ----------------------------------------------------------------------------
258// event/message handlers
259// ----------------------------------------------------------------------------
2bda0e17 260
33ac7e6f 261bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
edccf428
VZ
262{
263 bool processed = FALSE;
57c0af52 264 switch ( param )
edccf428 265 {
a95e38c0
VZ
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
57c0af52
VZ
269 processed = SendClickEvent();
270 break;
edccf428 271 }
2bda0e17 272
edccf428 273 return processed;
2bda0e17
KB
274}
275
678cd6de
VZ
276long wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
277{
be4017f8
VZ
278 // when we receive focus, we want to become the default button in our
279 // parent panel
280 if ( nMsg == WM_SETFOCUS )
678cd6de 281 {
be4017f8
VZ
282 SetDefault();
283
284 // let the default processign take place too
678cd6de 285 }
a95e38c0
VZ
286 else if ( nMsg == WM_LBUTTONDBLCLK )
287 {
f6bcfd97
BP
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
a95e38c0 293 }
678cd6de
VZ
294
295 // let the base class do all real processing
296 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
297}
cd0b1709
VZ
298
299// ----------------------------------------------------------------------------
300// owner-drawn buttons support
301// ----------------------------------------------------------------------------
302
303#ifdef __WIN32__
304
305// drawing helpers
306
307static 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
322static 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
331void wxButton::MakeOwnerDrawn()
332{
333 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
9750fc42 334 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
cd0b1709
VZ
335 {
336 // make it so
337 style |= BS_OWNERDRAW;
338 SetWindowLong(GetHwnd(), GWL_STYLE, style);
339 }
340}
341
342bool 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
357bool 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
16162a64
GRG
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
cd0b1709
VZ
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
16162a64
GRG
389 BWHHHHHHHHHHHHHHGBB
390 BWH GBB
391 BWH GBB
cd0b1709
VZ
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
16162a64 403 BG GB
cd0b1709
VZ
404 BGGGGGGGGGGGGGGGGGB
405 BBBBBBBBBBBBBBBBBBB
406*/
407
408static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
409 bool selected, bool pushed)
410{
411 RECT r;
412 CopyRect(&r, &rectBtn);
413
16162a64
GRG
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));
cd0b1709
VZ
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
16162a64
GRG
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
cd0b1709
VZ
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);
16162a64 464 DeleteObject(hpenLightGr);
cd0b1709
VZ
465 DeleteObject(hpenGrey);
466 DeleteObject(hpenBlack);
467}
468
469bool 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
9750fc42
VZ
514 if ( pushed )
515 {
516 // the label is shifted by 1 pixel to create "pushed" effect
517 OffsetRect(&rectBtn, 1, 1);
518 }
519
cd0b1709
VZ
520 DrawButtonText(hdc, &rectBtn, GetLabel(),
521 state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT)
522 : colFg);
523
524 ::DeleteObject(hbrushBackground);
525
cd0b1709
VZ
526 return TRUE;
527}
528
529#endif // __WIN32__