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