support multiline labels in wxCheckBox (#9495)
[wxWidgets.git] / src / msw / button.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/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
9 // Licence: wxWindows licence
10 /////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 // For compilers that support precompilation, includes "wx.h".
21 #include "wx/wxprec.h"
22
23 #ifdef __BORLANDC__
24 #pragma hdrstop
25 #endif
26
27 #if wxUSE_BUTTON
28
29 #include "wx/button.h"
30
31 #ifndef WX_PRECOMP
32 #include "wx/app.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 #include "wx/dcclient.h"
39 #include "wx/toplevel.h"
40 #endif
41
42 #include "wx/stockitem.h"
43 #include "wx/msw/private.h"
44 #include "wx/msw/private/button.h"
45
46 #if wxUSE_UXTHEME
47 #include "wx/msw/uxtheme.h"
48
49 // no need to include tmschema.h
50 #ifndef BP_PUSHBUTTON
51 #define BP_PUSHBUTTON 1
52
53 #define PBS_NORMAL 1
54 #define PBS_HOT 2
55 #define PBS_PRESSED 3
56 #define PBS_DISABLED 4
57 #define PBS_DEFAULTED 5
58
59 #define TMT_CONTENTMARGINS 3602
60 #endif
61 #endif // wxUSE_UXTHEME
62
63 #ifndef WM_THEMECHANGED
64 #define WM_THEMECHANGED 0x031A
65 #endif
66
67 #ifndef ODS_NOACCEL
68 #define ODS_NOACCEL 0x0100
69 #endif
70
71 #ifndef ODS_NOFOCUSRECT
72 #define ODS_NOFOCUSRECT 0x0200
73 #endif
74
75 // ----------------------------------------------------------------------------
76 // macros
77 // ----------------------------------------------------------------------------
78
79 #if wxUSE_EXTENDED_RTTI
80
81 WX_DEFINE_FLAGS( wxButtonStyle )
82
83 wxBEGIN_FLAGS( wxButtonStyle )
84 // new style border flags, we put them first to
85 // use them for streaming out
86 wxFLAGS_MEMBER(wxBORDER_SIMPLE)
87 wxFLAGS_MEMBER(wxBORDER_SUNKEN)
88 wxFLAGS_MEMBER(wxBORDER_DOUBLE)
89 wxFLAGS_MEMBER(wxBORDER_RAISED)
90 wxFLAGS_MEMBER(wxBORDER_STATIC)
91 wxFLAGS_MEMBER(wxBORDER_NONE)
92
93 // old style border flags
94 wxFLAGS_MEMBER(wxSIMPLE_BORDER)
95 wxFLAGS_MEMBER(wxSUNKEN_BORDER)
96 wxFLAGS_MEMBER(wxDOUBLE_BORDER)
97 wxFLAGS_MEMBER(wxRAISED_BORDER)
98 wxFLAGS_MEMBER(wxSTATIC_BORDER)
99 wxFLAGS_MEMBER(wxBORDER)
100
101 // standard window styles
102 wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
103 wxFLAGS_MEMBER(wxCLIP_CHILDREN)
104 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
105 wxFLAGS_MEMBER(wxWANTS_CHARS)
106 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
107 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
108 wxFLAGS_MEMBER(wxVSCROLL)
109 wxFLAGS_MEMBER(wxHSCROLL)
110
111 wxFLAGS_MEMBER(wxBU_LEFT)
112 wxFLAGS_MEMBER(wxBU_RIGHT)
113 wxFLAGS_MEMBER(wxBU_TOP)
114 wxFLAGS_MEMBER(wxBU_BOTTOM)
115 wxFLAGS_MEMBER(wxBU_EXACTFIT)
116 wxEND_FLAGS( wxButtonStyle )
117
118 IMPLEMENT_DYNAMIC_CLASS_XTI(wxButton, wxControl,"wx/button.h")
119
120 wxBEGIN_PROPERTIES_TABLE(wxButton)
121 wxEVENT_PROPERTY( Click , wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEvent)
122
123 wxPROPERTY( Font , wxFont , SetFont , GetFont , EMPTY_MACROVALUE, 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
124 wxPROPERTY( Label, wxString , SetLabel, GetLabel, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
125
126 wxPROPERTY_FLAGS( WindowStyle , wxButtonStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
127
128 wxEND_PROPERTIES_TABLE()
129
130 wxBEGIN_HANDLERS_TABLE(wxButton)
131 wxEND_HANDLERS_TABLE()
132
133 wxCONSTRUCTOR_6( wxButton , wxWindow* , Parent , wxWindowID , Id , wxString , Label , wxPoint , Position , wxSize , Size , long , WindowStyle )
134
135
136 #else
137 IMPLEMENT_DYNAMIC_CLASS(wxButton, wxControl)
138 #endif
139
140 // this macro tries to adjust the default button height to a reasonable value
141 // using the char height as the base
142 #define BUTTON_HEIGHT_FROM_CHAR_HEIGHT(cy) (11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy)/10)
143
144 // ============================================================================
145 // implementation
146 // ============================================================================
147
148 // ----------------------------------------------------------------------------
149 // creation/destruction
150 // ----------------------------------------------------------------------------
151
152 bool wxButton::Create(wxWindow *parent,
153 wxWindowID id,
154 const wxString& lbl,
155 const wxPoint& pos,
156 const wxSize& size,
157 long style,
158 const wxValidator& validator,
159 const wxString& name)
160 {
161 wxString label(lbl);
162 if (label.empty() && wxIsStockID(id))
163 {
164 // On Windows, some buttons aren't supposed to have mnemonics
165 label = wxGetStockLabel
166 (
167 id,
168 id == wxID_OK || id == wxID_CANCEL || id == wxID_CLOSE
169 ? wxSTOCK_NOFLAGS
170 : wxSTOCK_WITH_MNEMONIC
171 );
172 }
173
174 if ( !CreateControl(parent, id, pos, size, style, validator, name) )
175 return false;
176
177 WXDWORD exstyle;
178 WXDWORD msStyle = MSWGetStyle(style, &exstyle);
179
180 // if the label contains several lines we must explicitly tell the button
181 // about it or it wouldn't draw it correctly ("\n"s would just appear as
182 // black boxes)
183 //
184 // NB: we do it here and not in MSWGetStyle() because we need the label
185 // value and the label is not set yet when MSWGetStyle() is called
186 msStyle |= wxMSWButton::GetMultilineStyle(label);
187
188 return MSWCreateControl(_T("BUTTON"), msStyle, pos, size, label, exstyle);
189 }
190
191 wxButton::~wxButton()
192 {
193 wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
194 if ( tlw && tlw->GetTmpDefaultItem() == this )
195 {
196 UnsetTmpDefault();
197 }
198 }
199
200 // ----------------------------------------------------------------------------
201 // flags
202 // ----------------------------------------------------------------------------
203
204 WXDWORD wxButton::MSWGetStyle(long style, WXDWORD *exstyle) const
205 {
206 // buttons never have an external border, they draw their own one
207 WXDWORD msStyle = wxControl::MSWGetStyle
208 (
209 (style & ~wxBORDER_MASK) | wxBORDER_NONE, exstyle
210 );
211
212 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
213 // each other in any resizeable dialog which has more than one button in
214 // the bottom
215 msStyle |= WS_CLIPSIBLINGS;
216
217 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
218 // and wxBU_RIGHT to get BS_CENTER!
219 if ( style & wxBU_LEFT )
220 msStyle |= BS_LEFT;
221 if ( style & wxBU_RIGHT )
222 msStyle |= BS_RIGHT;
223 if ( style & wxBU_TOP )
224 msStyle |= BS_TOP;
225 if ( style & wxBU_BOTTOM )
226 msStyle |= BS_BOTTOM;
227 #ifndef __WXWINCE__
228 // flat 2d buttons
229 if ( style & wxNO_BORDER )
230 msStyle |= BS_FLAT;
231 #endif // __WXWINCE__
232
233 return msStyle;
234 }
235
236 void wxButton::SetLabel(const wxString& label)
237 {
238 wxMSWButton::UpdateMultilineStyle(GetHwnd(), label);
239
240 wxButtonBase::SetLabel(label);
241 }
242
243 // ----------------------------------------------------------------------------
244 // size management including autosizing
245 // ----------------------------------------------------------------------------
246
247 wxSize wxButton::DoGetBestSize() const
248 {
249 wxClientDC dc(wx_const_cast(wxButton *, this));
250 dc.SetFont(GetFont());
251
252 wxCoord wBtn,
253 hBtn;
254 dc.GetMultiLineTextExtent(GetLabelText(), &wBtn, &hBtn);
255
256 // add a margin -- the button is wider than just its label
257 wBtn += 3*GetCharWidth();
258 hBtn = BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hBtn);
259
260 // all buttons have at least the standard size unless the user explicitly
261 // wants them to be of smaller size and used wxBU_EXACTFIT style when
262 // creating the button
263 if ( !HasFlag(wxBU_EXACTFIT) )
264 {
265 wxSize sz = GetDefaultSize();
266 if (wBtn > sz.x)
267 sz.x = wBtn;
268 if (hBtn > sz.y)
269 sz.y = hBtn;
270
271 return sz;
272 }
273
274 wxSize best(wBtn, hBtn);
275 CacheBestSize(best);
276 return best;
277 }
278
279 /* static */
280 wxSize wxButtonBase::GetDefaultSize()
281 {
282 static wxSize s_sizeBtn;
283
284 if ( s_sizeBtn.x == 0 )
285 {
286 wxScreenDC dc;
287 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
288
289 // the size of a standard button in the dialog units is 50x14,
290 // translate this to pixels
291 // NB1: the multipliers come from the Windows convention
292 // NB2: the extra +1/+2 were needed to get the size be the same as the
293 // size of the buttons in the standard dialog - I don't know how
294 // this happens, but on my system this size is 75x23 in pixels and
295 // 23*8 isn't even divisible by 14... Would be nice to understand
296 // why these constants are needed though!
297 s_sizeBtn.x = (50 * (dc.GetCharWidth() + 1))/4;
298 s_sizeBtn.y = ((14 * dc.GetCharHeight()) + 2)/8;
299 }
300
301 return s_sizeBtn;
302 }
303
304 // ----------------------------------------------------------------------------
305 // default button handling
306 // ----------------------------------------------------------------------------
307
308 /*
309 "Everything you ever wanted to know about the default buttons" or "Why do we
310 have to do all this?"
311
312 In MSW the default button should be activated when the user presses Enter
313 and the current control doesn't process Enter itself somehow. This is
314 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
315 Another aspect of "defaultness" is that the default button has different
316 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
317 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
318 be unset if our parent window is not active so it should be unset whenever
319 we lose activation and set back when we regain it.
320
321 Final complication is that when a button is active, it should be the default
322 one, i.e. pressing Enter on a button always activates it and not another
323 one.
324
325 We handle this by maintaining a permanent and a temporary default items in
326 wxControlContainer (both may be NULL). When a button becomes the current
327 control (i.e. gets focus) it sets itself as the temporary default which
328 ensures that it has the right appearance and that Enter will be redirected
329 to it. When the button loses focus, it unsets the temporary default and so
330 the default item will be the permanent default -- that is the default button
331 if any had been set or none otherwise, which is just what we want.
332
333 NB: all this is quite complicated by now and the worst is that normally
334 it shouldn't be necessary at all as for the normal Windows programs
335 DefWindowProc() and IsDialogMessage() take care of all this
336 automatically -- however in wxWidgets programs this doesn't work for
337 nested hierarchies (i.e. a notebook inside a notebook) for unknown
338 reason and so we have to reproduce all this code ourselves. It would be
339 very nice if we could avoid doing it.
340 */
341
342 // set this button as the (permanently) default one in its panel
343 wxWindow *wxButton::SetDefault()
344 {
345 // set this one as the default button both for wxWidgets ...
346 wxWindow *winOldDefault = wxButtonBase::SetDefault();
347
348 // ... and Windows
349 SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), false);
350 SetDefaultStyle(this, true);
351
352 return winOldDefault;
353 }
354
355 // return the top level parent window if it's not being deleted yet, otherwise
356 // return NULL
357 static wxTopLevelWindow *GetTLWParentIfNotBeingDeleted(wxWindow *win)
358 {
359 for ( ;; )
360 {
361 // IsTopLevel() will return false for a wxTLW being deleted, so we also
362 // need the parent test for this case
363 wxWindow * const parent = win->GetParent();
364 if ( !parent || win->IsTopLevel() )
365 {
366 if ( win->IsBeingDeleted() )
367 return NULL;
368
369 break;
370 }
371
372 win = parent;
373 }
374
375 wxASSERT_MSG( win, _T("button without top level parent?") );
376
377 wxTopLevelWindow * const tlw = wxDynamicCast(win, wxTopLevelWindow);
378 wxASSERT_MSG( tlw, _T("logic error in GetTLWParentIfNotBeingDeleted()") );
379
380 return tlw;
381 }
382
383 // set this button as being currently default
384 void wxButton::SetTmpDefault()
385 {
386 wxTopLevelWindow * const tlw = GetTLWParentIfNotBeingDeleted(GetParent());
387 if ( !tlw )
388 return;
389
390 wxWindow *winOldDefault = tlw->GetDefaultItem();
391 tlw->SetTmpDefaultItem(this);
392
393 SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), false);
394 SetDefaultStyle(this, true);
395 }
396
397 // unset this button as currently default, it may still stay permanent default
398 void wxButton::UnsetTmpDefault()
399 {
400 wxTopLevelWindow * const tlw = GetTLWParentIfNotBeingDeleted(GetParent());
401 if ( !tlw )
402 return;
403
404 tlw->SetTmpDefaultItem(NULL);
405
406 wxWindow *winOldDefault = tlw->GetDefaultItem();
407
408 SetDefaultStyle(this, false);
409 SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), true);
410 }
411
412 /* static */
413 void
414 wxButton::SetDefaultStyle(wxButton *btn, bool on)
415 {
416 // we may be called with NULL pointer -- simpler to do the check here than
417 // in the caller which does wxDynamicCast()
418 if ( !btn )
419 return;
420
421 // first, let DefDlgProc() know about the new default button
422 if ( on )
423 {
424 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
425 // focus at all any more
426 if ( !wxTheApp->IsActive() )
427 return;
428
429 wxWindow * const tlw = wxGetTopLevelParent(btn);
430 wxCHECK_RET( tlw, _T("button without top level window?") );
431
432 ::SendMessage(GetHwndOf(tlw), DM_SETDEFID, btn->GetId(), 0L);
433
434 // sending DM_SETDEFID also changes the button style to
435 // BS_DEFPUSHBUTTON so there is nothing more to do
436 }
437
438 // then also change the style as needed
439 long style = ::GetWindowLong(GetHwndOf(btn), GWL_STYLE);
440 if ( !(style & BS_DEFPUSHBUTTON) == on )
441 {
442 // don't do it with the owner drawn buttons because it will
443 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
444 // BS_DEFPUSHBUTTON != 0)!
445 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
446 {
447 ::SendMessage(GetHwndOf(btn), BM_SETSTYLE,
448 on ? style | BS_DEFPUSHBUTTON
449 : style & ~BS_DEFPUSHBUTTON,
450 1L /* redraw */);
451 }
452 else // owner drawn
453 {
454 // redraw the button - it will notice itself that it's
455 // [not] the default one [any longer]
456 btn->Refresh();
457 }
458 }
459 //else: already has correct style
460 }
461
462 // ----------------------------------------------------------------------------
463 // helpers
464 // ----------------------------------------------------------------------------
465
466 bool wxButton::SendClickEvent()
467 {
468 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
469 event.SetEventObject(this);
470
471 return ProcessCommand(event);
472 }
473
474 void wxButton::Command(wxCommandEvent & event)
475 {
476 ProcessCommand(event);
477 }
478
479 // ----------------------------------------------------------------------------
480 // event/message handlers
481 // ----------------------------------------------------------------------------
482
483 bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
484 {
485 bool processed = false;
486 switch ( param )
487 {
488 // NOTE: Apparently older versions (NT 4?) of the common controls send
489 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
490 // buttons, so in order to send two EVT_BUTTON events we should
491 // catch both types. Currently (Feb 2003) up-to-date versions of
492 // win98, win2k and winXP all send two BN_CLICKED messages for
493 // all button types, so we don't catch BN_DOUBLECLICKED anymore
494 // in order to not get 3 EVT_BUTTON events. If this is a problem
495 // then we need to figure out which version of the comctl32 changed
496 // this behaviour and test for it.
497
498 case 1: // message came from an accelerator
499 case BN_CLICKED: // normal buttons send this
500 processed = SendClickEvent();
501 break;
502 }
503
504 return processed;
505 }
506
507 WXLRESULT wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
508 {
509 // when we receive focus, we want to temporarily become the default button in
510 // our parent panel so that pressing "Enter" would activate us -- and when
511 // losing it we should restore the previous default button as well
512 if ( nMsg == WM_SETFOCUS )
513 {
514 SetTmpDefault();
515
516 // let the default processing take place too
517 }
518 else if ( nMsg == WM_KILLFOCUS )
519 {
520 UnsetTmpDefault();
521 }
522 else if ( nMsg == WM_LBUTTONDBLCLK )
523 {
524 // emulate a click event to force an owner-drawn button to change its
525 // appearance - without this, it won't do it
526 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN, wParam, lParam);
527
528 // and continue with processing the message normally as well
529 }
530 #if wxUSE_UXTHEME
531 else if ( nMsg == WM_THEMECHANGED )
532 {
533 // need to recalculate the best size here
534 // as the theme size might have changed
535 InvalidateBestSize();
536 }
537 else if ( wxUxThemeEngine::GetIfActive() )
538 {
539 // we need to Refresh() if mouse has entered or left window
540 // so we can update the hot tracking state
541 // must use m_mouseInWindow here instead of IsMouseInWindow()
542 // since we need to know the first time the mouse enters the window
543 // and IsMouseInWindow() would return true in this case
544 if ( ( nMsg == WM_MOUSEMOVE && !m_mouseInWindow ) ||
545 nMsg == WM_MOUSELEAVE )
546 {
547 Refresh();
548 }
549 }
550 #endif // wxUSE_UXTHEME
551
552 // let the base class do all real processing
553 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
554 }
555
556 // ----------------------------------------------------------------------------
557 // owner-drawn buttons support
558 // ----------------------------------------------------------------------------
559
560 // drawing helpers
561
562 static void DrawButtonText(HDC hdc,
563 RECT *pRect,
564 const wxString& text,
565 COLORREF col)
566 {
567 COLORREF colOld = SetTextColor(hdc, col);
568 int modeOld = SetBkMode(hdc, TRANSPARENT);
569
570 if ( text.find(_T('\n')) != wxString::npos )
571 {
572 // draw multiline label
573
574 // first we need to compute its bounding rect
575 RECT rc;
576 ::CopyRect(&rc, pRect);
577 ::DrawText(hdc, text.wx_str(), text.length(), &rc,
578 DT_CENTER | DT_CALCRECT);
579
580 // now center this rect inside the entire button area
581 const LONG w = rc.right - rc.left;
582 const LONG h = rc.bottom - rc.top;
583 rc.left = (pRect->right - pRect->left)/2 - w/2;
584 rc.right = rc.left+w;
585 rc.top = (pRect->bottom - pRect->top)/2 - h/2;
586 rc.bottom = rc.top+h;
587
588 ::DrawText(hdc, text.wx_str(), text.length(), &rc, DT_CENTER);
589 }
590 else // single line label
591 {
592 // Note: we must have DT_SINGLELINE for DT_VCENTER to work.
593 ::DrawText(hdc, text.wx_str(), text.length(), pRect,
594 DT_SINGLELINE | DT_CENTER | DT_VCENTER);
595 }
596
597 SetBkMode(hdc, modeOld);
598 SetTextColor(hdc, colOld);
599 }
600
601 static void DrawRect(HDC hdc, const RECT& r)
602 {
603 wxDrawLine(hdc, r.left, r.top, r.right, r.top);
604 wxDrawLine(hdc, r.right, r.top, r.right, r.bottom);
605 wxDrawLine(hdc, r.right, r.bottom, r.left, r.bottom);
606 wxDrawLine(hdc, r.left, r.bottom, r.left, r.top);
607 }
608
609 void wxButton::MakeOwnerDrawn()
610 {
611 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
612 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
613 {
614 // make it so
615 style |= BS_OWNERDRAW;
616 SetWindowLong(GetHwnd(), GWL_STYLE, style);
617 }
618 }
619
620 bool wxButton::SetBackgroundColour(const wxColour &colour)
621 {
622 if ( !wxControl::SetBackgroundColour(colour) )
623 {
624 // nothing to do
625 return false;
626 }
627
628 MakeOwnerDrawn();
629
630 Refresh();
631
632 return true;
633 }
634
635 bool wxButton::SetForegroundColour(const wxColour &colour)
636 {
637 if ( !wxControl::SetForegroundColour(colour) )
638 {
639 // nothing to do
640 return false;
641 }
642
643 MakeOwnerDrawn();
644
645 Refresh();
646
647 return true;
648 }
649
650 /*
651 The button frame looks like this normally:
652
653 WWWWWWWWWWWWWWWWWWB
654 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
655 WH GB H = light grey (LIGHT)
656 WH GB G = dark grey (SHADOW)
657 WH GB B = black (DKSHADOW)
658 WH GB
659 WGGGGGGGGGGGGGGGGGB
660 BBBBBBBBBBBBBBBBBBB
661
662 When the button is selected, the button becomes like this (the total button
663 size doesn't change):
664
665 BBBBBBBBBBBBBBBBBBB
666 BWWWWWWWWWWWWWWWWBB
667 BWHHHHHHHHHHHHHHGBB
668 BWH GBB
669 BWH GBB
670 BWGGGGGGGGGGGGGGGBB
671 BBBBBBBBBBBBBBBBBBB
672 BBBBBBBBBBBBBBBBBBB
673
674 When the button is pushed (while selected) it is like:
675
676 BBBBBBBBBBBBBBBBBBB
677 BGGGGGGGGGGGGGGGGGB
678 BG GB
679 BG GB
680 BG GB
681 BG GB
682 BGGGGGGGGGGGGGGGGGB
683 BBBBBBBBBBBBBBBBBBB
684 */
685
686 static void DrawButtonFrame(HDC hdc, const RECT& rectBtn,
687 bool selected, bool pushed)
688 {
689 RECT r;
690 CopyRect(&r, &rectBtn);
691
692 HPEN hpenBlack = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW)),
693 hpenGrey = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW)),
694 hpenLightGr = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT)),
695 hpenWhite = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT));
696
697 HPEN hpenOld = (HPEN)SelectObject(hdc, hpenBlack);
698
699 r.right--;
700 r.bottom--;
701
702 if ( pushed )
703 {
704 DrawRect(hdc, r);
705
706 (void)SelectObject(hdc, hpenGrey);
707 ::InflateRect(&r, -1, -1);
708
709 DrawRect(hdc, r);
710 }
711 else // !pushed
712 {
713 if ( selected )
714 {
715 DrawRect(hdc, r);
716
717 ::InflateRect(&r, -1, -1);
718 }
719
720 wxDrawLine(hdc, r.left, r.bottom, r.right, r.bottom);
721 wxDrawLine(hdc, r.right, r.bottom, r.right, r.top - 1);
722
723 (void)SelectObject(hdc, hpenWhite);
724 wxDrawLine(hdc, r.left, r.bottom - 1, r.left, r.top);
725 wxDrawLine(hdc, r.left, r.top, r.right, r.top);
726
727 (void)SelectObject(hdc, hpenLightGr);
728 wxDrawLine(hdc, r.left + 1, r.bottom - 2, r.left + 1, r.top + 1);
729 wxDrawLine(hdc, r.left + 1, r.top + 1, r.right - 1, r.top + 1);
730
731 (void)SelectObject(hdc, hpenGrey);
732 wxDrawLine(hdc, r.left + 1, r.bottom - 1, r.right - 1, r.bottom - 1);
733 wxDrawLine(hdc, r.right - 1, r.bottom - 1, r.right - 1, r.top);
734 }
735
736 (void)SelectObject(hdc, hpenOld);
737 DeleteObject(hpenWhite);
738 DeleteObject(hpenLightGr);
739 DeleteObject(hpenGrey);
740 DeleteObject(hpenBlack);
741 }
742
743 #if wxUSE_UXTHEME
744 static
745 void MSWDrawXPBackground(wxButton *button, WXDRAWITEMSTRUCT *wxdis)
746 {
747 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
748 HDC hdc = lpDIS->hDC;
749 UINT state = lpDIS->itemState;
750 RECT rectBtn;
751 CopyRect(&rectBtn, &lpDIS->rcItem);
752
753 wxUxThemeHandle theme(button, L"BUTTON");
754 int iState;
755
756 if ( state & ODS_SELECTED )
757 {
758 iState = PBS_PRESSED;
759 }
760 else if ( button->HasCapture() || button->IsMouseInWindow() )
761 {
762 iState = PBS_HOT;
763 }
764 else if ( state & ODS_FOCUS )
765 {
766 iState = PBS_DEFAULTED;
767 }
768 else if ( state & ODS_DISABLED )
769 {
770 iState = PBS_DISABLED;
771 }
772 else
773 {
774 iState = PBS_NORMAL;
775 }
776
777 // draw parent background if needed
778 if ( wxUxThemeEngine::Get()->IsThemeBackgroundPartiallyTransparent(theme,
779 BP_PUSHBUTTON,
780 iState) )
781 {
782 wxUxThemeEngine::Get()->DrawThemeParentBackground(GetHwndOf(button), hdc, &rectBtn);
783 }
784
785 // draw background
786 wxUxThemeEngine::Get()->DrawThemeBackground(theme, hdc, BP_PUSHBUTTON, iState,
787 &rectBtn, NULL);
788
789 // calculate content area margins
790 MARGINS margins;
791 wxUxThemeEngine::Get()->GetThemeMargins(theme, hdc, BP_PUSHBUTTON, iState,
792 TMT_CONTENTMARGINS, &rectBtn, &margins);
793 RECT rectClient;
794 ::CopyRect(&rectClient, &rectBtn);
795 ::InflateRect(&rectClient, -margins.cxLeftWidth, -margins.cyTopHeight);
796
797 // if focused and !nofocus rect
798 if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) )
799 {
800 DrawFocusRect(hdc, &rectClient);
801 }
802
803 if ( button->UseBgCol() )
804 {
805 COLORREF colBg = wxColourToRGB(button->GetBackgroundColour());
806 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
807
808 // don't overwrite the focus rect
809 ::InflateRect(&rectClient, -1, -1);
810 FillRect(hdc, &rectClient, hbrushBackground);
811 ::DeleteObject(hbrushBackground);
812 }
813 }
814 #endif // wxUSE_UXTHEME
815
816 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
817 {
818 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
819 HDC hdc = lpDIS->hDC;
820 UINT state = lpDIS->itemState;
821 RECT rectBtn;
822 CopyRect(&rectBtn, &lpDIS->rcItem);
823
824 #if wxUSE_UXTHEME
825 if ( wxUxThemeEngine::GetIfActive() )
826 {
827 MSWDrawXPBackground(this, wxdis);
828 }
829 else
830 #endif // wxUSE_UXTHEME
831 {
832 COLORREF colBg = wxColourToRGB(GetBackgroundColour());
833
834 // first, draw the background
835 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
836 FillRect(hdc, &rectBtn, hbrushBackground);
837 ::DeleteObject(hbrushBackground);
838
839 // draw the border for the current state
840 bool selected = (state & ODS_SELECTED) != 0;
841 if ( !selected )
842 {
843 wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
844 if ( tlw )
845 {
846 selected = tlw->GetDefaultItem() == this;
847 }
848 }
849 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
850
851 DrawButtonFrame(hdc, rectBtn, selected, pushed);
852
853 // if focused and !nofocus rect
854 if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) )
855 {
856 RECT rectFocus;
857 CopyRect(&rectFocus, &rectBtn);
858
859 // I don't know where does this constant come from, but this is how
860 // Windows draws them
861 InflateRect(&rectFocus, -4, -4);
862
863 DrawFocusRect(hdc, &rectFocus);
864 }
865
866 if ( pushed )
867 {
868 // the label is shifted by 1 pixel to create "pushed" effect
869 OffsetRect(&rectBtn, 1, 1);
870 }
871 }
872
873 COLORREF colFg = wxColourToRGB(GetForegroundColour());
874 if ( state & ODS_DISABLED ) colFg = GetSysColor(COLOR_GRAYTEXT) ;
875 wxString label = GetLabel();
876 if ( state & ODS_NOACCEL ) label = GetLabelText() ;
877 DrawButtonText(hdc, &rectBtn, label, colFg);
878
879 return true;
880 }
881
882 #endif // wxUSE_BUTTON