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