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