implement button support for pre-XP systems (or with themes disabled); using only...
[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 #include "wx/imaglist.h"
41 #endif
42
43 #include "wx/stockitem.h"
44 #include "wx/msw/private.h"
45 #include "wx/msw/private/button.h"
46 #include "wx/msw/private/dc.h"
47
48 using namespace wxMSWImpl;
49
50 #if wxUSE_UXTHEME
51 #include "wx/msw/uxtheme.h"
52
53 // no need to include tmschema.h
54 #ifndef BP_PUSHBUTTON
55 #define BP_PUSHBUTTON 1
56
57 #define PBS_NORMAL 1
58 #define PBS_HOT 2
59 #define PBS_PRESSED 3
60 #define PBS_DISABLED 4
61 #define PBS_DEFAULTED 5
62
63 #define TMT_CONTENTMARGINS 3602
64 #endif
65
66 // provide the necessary declarations ourselves if they're missing from
67 // headers
68 #ifndef BCM_SETIMAGELIST
69 #define BCM_SETIMAGELIST 0x1602
70 #define BCM_SETTEXTMARGIN 0x1604
71
72 enum
73 {
74 BUTTON_IMAGELIST_ALIGN_LEFT,
75 BUTTON_IMAGELIST_ALIGN_RIGHT,
76 BUTTON_IMAGELIST_ALIGN_TOP,
77 BUTTON_IMAGELIST_ALIGN_BOTTOM
78 };
79
80 struct BUTTON_IMAGELIST
81 {
82 HIMAGELIST himl;
83 RECT margin;
84 UINT uAlign;
85 };
86 #endif
87 #endif // wxUSE_UXTHEME
88
89 #ifndef WM_THEMECHANGED
90 #define WM_THEMECHANGED 0x031A
91 #endif
92
93 #ifndef ODS_NOACCEL
94 #define ODS_NOACCEL 0x0100
95 #endif
96
97 #ifndef ODS_NOFOCUSRECT
98 #define ODS_NOFOCUSRECT 0x0200
99 #endif
100
101 #ifndef DT_HIDEPREFIX
102 #define DT_HIDEPREFIX 0x00100000
103 #endif
104
105 // ----------------------------------------------------------------------------
106 // button image data
107 // ----------------------------------------------------------------------------
108
109 // we use different data classes for owner drawn buttons and for themed XP ones
110
111 class wxButtonImageData
112 {
113 public:
114 wxButtonImageData() { }
115 virtual ~wxButtonImageData() { }
116
117 virtual wxBitmap GetBitmap(wxButton::State which) const = 0;
118 virtual void SetBitmap(const wxBitmap& bitmap, wxButton::State which) = 0;
119
120 virtual wxSize GetBitmapMargins() const = 0;
121 virtual void SetBitmapMargins(wxCoord x, wxCoord y) = 0;
122
123 virtual wxDirection GetBitmapPosition() const = 0;
124 virtual void SetBitmapPosition(wxDirection dir) = 0;
125
126 private:
127 wxDECLARE_NO_COPY_CLASS(wxButtonImageData);
128 };
129
130 namespace
131 {
132
133 // the gap between button edge and the interior area used by Windows for the
134 // standard buttons
135 const int OD_BUTTON_MARGIN = 4;
136
137 class wxODButtonImageData : public wxButtonImageData
138 {
139 public:
140 wxODButtonImageData(wxButton *btn)
141 {
142 m_dir = wxLEFT;
143
144 m_margin.x = btn->GetCharWidth();
145 m_margin.y = btn->GetCharHeight() / 2;
146 }
147
148 virtual wxBitmap GetBitmap(wxButton::State which) const
149 {
150 return m_bitmaps[which];
151 }
152
153 virtual void SetBitmap(const wxBitmap& bitmap, wxButton::State which)
154 {
155 m_bitmaps[which] = bitmap;
156 }
157
158 virtual wxSize GetBitmapMargins() const
159 {
160 return m_margin + wxSize(OD_BUTTON_MARGIN, OD_BUTTON_MARGIN);
161 }
162
163 virtual void SetBitmapMargins(wxCoord x, wxCoord y)
164 {
165 m_margin = wxSize(x, y);
166 }
167
168 virtual wxDirection GetBitmapPosition() const
169 {
170 return m_dir;
171 }
172
173 virtual void SetBitmapPosition(wxDirection dir)
174 {
175 m_dir = dir;
176 }
177
178 private:
179 // just store the values passed to us to be able to retrieve them later
180 // from the drawing code
181 wxBitmap m_bitmaps[wxButton::State_Max];
182 wxSize m_margin;
183 wxDirection m_dir;
184
185 wxDECLARE_NO_COPY_CLASS(wxODButtonImageData);
186 };
187
188 #if wxUSE_UXTHEME
189
190 class wxXPButtonImageData : public wxButtonImageData
191 {
192 public:
193 // we must be constructed with the size of our images as we need to create
194 // the image list
195 wxXPButtonImageData(wxButton *btn, const wxSize& size)
196 : m_iml(size.x, size.y, true /* use mask */, wxButton::State_Max),
197 m_hwndBtn(GetHwndOf(btn))
198 {
199 m_data.himl = GetHimagelistOf(&m_iml);
200
201 // use default margins
202 m_data.margin.left =
203 m_data.margin.right = btn->GetCharWidth();
204 m_data.margin.top =
205 m_data.margin.bottom = btn->GetCharHeight() / 2;
206
207 // and default alignment
208 m_data.uAlign = BUTTON_IMAGELIST_ALIGN_LEFT;
209 }
210
211 virtual wxBitmap GetBitmap(wxButton::State which) const
212 {
213 return m_iml.GetBitmap(which);
214 }
215
216 virtual void SetBitmap(const wxBitmap& bitmap, wxButton::State which)
217 {
218 const int imagesToAdd = which - m_iml.GetImageCount();
219 if ( imagesToAdd >= 0 )
220 {
221 if ( imagesToAdd > 0 )
222 {
223 const wxBitmap bmpNormal = GetBitmap(wxButton::State_Normal);
224 for ( int n = 0; n < imagesToAdd; n++ )
225 m_iml.Add(bmpNormal);
226 }
227
228 m_iml.Add(bitmap);
229 }
230 else // we already have this bitmap
231 {
232 m_iml.Replace(which, bitmap);
233 }
234
235 UpdateImageInfo();
236 }
237
238 virtual wxSize GetBitmapMargins() const
239 {
240 return wxSize(m_data.margin.left, m_data.margin.top);
241 }
242
243 virtual void SetBitmapMargins(wxCoord x, wxCoord y)
244 {
245 RECT& margin = m_data.margin;
246 margin.left =
247 margin.right = x;
248 margin.top =
249 margin.bottom = y;
250
251 if ( !::SendMessage(m_hwndBtn, BCM_SETTEXTMARGIN, 0, (LPARAM)&margin) )
252 {
253 wxLogDebug("SendMessage(BCM_SETTEXTMARGIN) failed");
254 }
255 }
256
257 virtual wxDirection GetBitmapPosition() const
258 {
259 switch ( m_data.uAlign )
260 {
261 default:
262 wxFAIL_MSG( "invalid image alignment" );
263 // fall through
264
265 case BUTTON_IMAGELIST_ALIGN_LEFT:
266 return wxLEFT;
267
268 case BUTTON_IMAGELIST_ALIGN_RIGHT:
269 return wxRIGHT;
270
271 case BUTTON_IMAGELIST_ALIGN_TOP:
272 return wxTOP;
273
274 case BUTTON_IMAGELIST_ALIGN_BOTTOM:
275 return wxBOTTOM;
276 }
277 }
278
279 virtual void SetBitmapPosition(wxDirection dir)
280 {
281 UINT alignNew;
282 switch ( dir )
283 {
284 default:
285 wxFAIL_MSG( "invalid direction" );
286 // fall through
287
288 case wxLEFT:
289 alignNew = BUTTON_IMAGELIST_ALIGN_LEFT;
290 break;
291
292 case wxRIGHT:
293 alignNew = BUTTON_IMAGELIST_ALIGN_RIGHT;
294 break;
295
296 case wxTOP:
297 alignNew = BUTTON_IMAGELIST_ALIGN_TOP;
298 break;
299
300 case wxBOTTOM:
301 alignNew = BUTTON_IMAGELIST_ALIGN_BOTTOM;
302 break;
303 }
304
305 if ( alignNew != m_data.uAlign )
306 {
307 m_data.uAlign = alignNew;
308 UpdateImageInfo();
309 }
310 }
311
312 private:
313 void UpdateImageInfo()
314 {
315 if ( !::SendMessage(m_hwndBtn, BCM_SETIMAGELIST, 0, (LPARAM)&m_data) )
316 {
317 wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed");
318 }
319 }
320
321 // we store image list separately to be able to use convenient wxImageList
322 // methods instead of working with raw HIMAGELIST
323 wxImageList m_iml;
324
325 // store the rest of the data in BCM_SETIMAGELIST-friendly form
326 BUTTON_IMAGELIST m_data;
327
328 // the button we're associated with
329 const HWND m_hwndBtn;
330
331
332 wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData);
333 };
334
335 #endif // wxUSE_UXTHEME
336
337 } // anonymous namespace
338
339 // ----------------------------------------------------------------------------
340 // macros
341 // ----------------------------------------------------------------------------
342
343 #if wxUSE_EXTENDED_RTTI
344
345 WX_DEFINE_FLAGS( wxButtonStyle )
346
347 wxBEGIN_FLAGS( wxButtonStyle )
348 // new style border flags, we put them first to
349 // use them for streaming out
350 wxFLAGS_MEMBER(wxBORDER_SIMPLE)
351 wxFLAGS_MEMBER(wxBORDER_SUNKEN)
352 wxFLAGS_MEMBER(wxBORDER_DOUBLE)
353 wxFLAGS_MEMBER(wxBORDER_RAISED)
354 wxFLAGS_MEMBER(wxBORDER_STATIC)
355 wxFLAGS_MEMBER(wxBORDER_NONE)
356
357 // old style border flags
358 wxFLAGS_MEMBER(wxSIMPLE_BORDER)
359 wxFLAGS_MEMBER(wxSUNKEN_BORDER)
360 wxFLAGS_MEMBER(wxDOUBLE_BORDER)
361 wxFLAGS_MEMBER(wxRAISED_BORDER)
362 wxFLAGS_MEMBER(wxSTATIC_BORDER)
363 wxFLAGS_MEMBER(wxBORDER)
364
365 // standard window styles
366 wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
367 wxFLAGS_MEMBER(wxCLIP_CHILDREN)
368 wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
369 wxFLAGS_MEMBER(wxWANTS_CHARS)
370 wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
371 wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
372 wxFLAGS_MEMBER(wxVSCROLL)
373 wxFLAGS_MEMBER(wxHSCROLL)
374
375 wxFLAGS_MEMBER(wxBU_LEFT)
376 wxFLAGS_MEMBER(wxBU_RIGHT)
377 wxFLAGS_MEMBER(wxBU_TOP)
378 wxFLAGS_MEMBER(wxBU_BOTTOM)
379 wxFLAGS_MEMBER(wxBU_EXACTFIT)
380 wxEND_FLAGS( wxButtonStyle )
381
382 IMPLEMENT_DYNAMIC_CLASS_XTI(wxButton, wxControl,"wx/button.h")
383
384 wxBEGIN_PROPERTIES_TABLE(wxButton)
385 wxEVENT_PROPERTY( Click , wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEvent)
386
387 wxPROPERTY( Font , wxFont , SetFont , GetFont , EMPTY_MACROVALUE, 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
388 wxPROPERTY( Label, wxString , SetLabel, GetLabel, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
389
390 wxPROPERTY_FLAGS( WindowStyle , wxButtonStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
391
392 wxEND_PROPERTIES_TABLE()
393
394 wxBEGIN_HANDLERS_TABLE(wxButton)
395 wxEND_HANDLERS_TABLE()
396
397 wxCONSTRUCTOR_6( wxButton , wxWindow* , Parent , wxWindowID , Id , wxString , Label , wxPoint , Position , wxSize , Size , long , WindowStyle )
398
399
400 #else
401 IMPLEMENT_DYNAMIC_CLASS(wxButton, wxControl)
402 #endif
403
404 // ============================================================================
405 // implementation
406 // ============================================================================
407
408 // ----------------------------------------------------------------------------
409 // helper functions from wx/msw/private/button.h
410 // ----------------------------------------------------------------------------
411
412 void wxMSWButton::UpdateMultilineStyle(HWND hwnd, const wxString& label)
413 {
414 // update BS_MULTILINE style depending on the new label (resetting it
415 // doesn't seem to do anything very useful but it shouldn't hurt and we do
416 // have to set it whenever the label becomes multi line as otherwise it
417 // wouldn't be shown correctly as we don't use BS_MULTILINE when creating
418 // the control unless it already has new lines in its label)
419 long styleOld = ::GetWindowLong(hwnd, GWL_STYLE),
420 styleNew;
421 if ( label.find(_T('\n')) != wxString::npos )
422 styleNew = styleOld | BS_MULTILINE;
423 else
424 styleNew = styleOld & ~BS_MULTILINE;
425
426 if ( styleNew != styleOld )
427 ::SetWindowLong(hwnd, GWL_STYLE, styleNew);
428 }
429
430 wxSize wxMSWButton::GetFittingSize(wxWindow *win, const wxSize& sizeLabel)
431 {
432 // FIXME: this is pure guesswork, need to retrieve the real button margins
433 wxSize sizeBtn = sizeLabel;
434
435 sizeBtn.x += 3*win->GetCharWidth();
436 sizeBtn.y = 11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(sizeLabel.y)/10;
437
438 return sizeBtn;
439 }
440
441 wxSize wxMSWButton::ComputeBestSize(wxControl *btn)
442 {
443 wxClientDC dc(btn);
444
445 wxSize sizeBtn;
446 dc.GetMultiLineTextExtent(btn->GetLabelText(), &sizeBtn.x, &sizeBtn.y);
447
448 sizeBtn = GetFittingSize(btn, sizeBtn);
449
450 // all buttons have at least the standard size unless the user explicitly
451 // wants them to be of smaller size and used wxBU_EXACTFIT style when
452 // creating the button
453 if ( !btn->HasFlag(wxBU_EXACTFIT) )
454 {
455 wxSize sizeDef = wxButton::GetDefaultSize();
456 if ( sizeBtn.x < sizeDef.x )
457 sizeBtn.x = sizeDef.x;
458 if ( sizeBtn.y < sizeDef.y )
459 sizeBtn.y = sizeDef.y;
460 }
461
462 btn->CacheBestSize(sizeBtn);
463
464 return sizeBtn;
465 }
466
467 // ----------------------------------------------------------------------------
468 // creation/destruction
469 // ----------------------------------------------------------------------------
470
471 bool wxButton::Create(wxWindow *parent,
472 wxWindowID id,
473 const wxString& lbl,
474 const wxPoint& pos,
475 const wxSize& size,
476 long style,
477 const wxValidator& validator,
478 const wxString& name)
479 {
480 wxString label(lbl);
481 if (label.empty() && wxIsStockID(id))
482 {
483 // On Windows, some buttons aren't supposed to have mnemonics
484 label = wxGetStockLabel
485 (
486 id,
487 id == wxID_OK || id == wxID_CANCEL || id == wxID_CLOSE
488 ? wxSTOCK_NOFLAGS
489 : wxSTOCK_WITH_MNEMONIC
490 );
491 }
492
493 if ( !CreateControl(parent, id, pos, size, style, validator, name) )
494 return false;
495
496 WXDWORD exstyle;
497 WXDWORD msStyle = MSWGetStyle(style, &exstyle);
498
499 // if the label contains several lines we must explicitly tell the button
500 // about it or it wouldn't draw it correctly ("\n"s would just appear as
501 // black boxes)
502 //
503 // NB: we do it here and not in MSWGetStyle() because we need the label
504 // value and the label is not set yet when MSWGetStyle() is called
505 msStyle |= wxMSWButton::GetMultilineStyle(label);
506
507 return MSWCreateControl(_T("BUTTON"), msStyle, pos, size, label, exstyle);
508 }
509
510 wxButton::~wxButton()
511 {
512 wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
513 if ( tlw && tlw->GetTmpDefaultItem() == this )
514 {
515 UnsetTmpDefault();
516 }
517
518 delete m_imageData;
519 }
520
521 // ----------------------------------------------------------------------------
522 // flags
523 // ----------------------------------------------------------------------------
524
525 WXDWORD wxButton::MSWGetStyle(long style, WXDWORD *exstyle) const
526 {
527 // buttons never have an external border, they draw their own one
528 WXDWORD msStyle = wxControl::MSWGetStyle
529 (
530 (style & ~wxBORDER_MASK) | wxBORDER_NONE, exstyle
531 );
532
533 // we must use WS_CLIPSIBLINGS with the buttons or they would draw over
534 // each other in any resizeable dialog which has more than one button in
535 // the bottom
536 msStyle |= WS_CLIPSIBLINGS;
537
538 // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT
539 // and wxBU_RIGHT to get BS_CENTER!
540 if ( style & wxBU_LEFT )
541 msStyle |= BS_LEFT;
542 if ( style & wxBU_RIGHT )
543 msStyle |= BS_RIGHT;
544 if ( style & wxBU_TOP )
545 msStyle |= BS_TOP;
546 if ( style & wxBU_BOTTOM )
547 msStyle |= BS_BOTTOM;
548 #ifndef __WXWINCE__
549 // flat 2d buttons
550 if ( style & wxNO_BORDER )
551 msStyle |= BS_FLAT;
552 #endif // __WXWINCE__
553
554 return msStyle;
555 }
556
557 void wxButton::SetLabel(const wxString& label)
558 {
559 wxMSWButton::UpdateMultilineStyle(GetHwnd(), label);
560
561 wxButtonBase::SetLabel(label);
562 }
563
564 // ----------------------------------------------------------------------------
565 // size management including autosizing
566 // ----------------------------------------------------------------------------
567
568 wxSize wxButton::DoGetBestSize() const
569 {
570 wxSize size = wxMSWButton::ComputeBestSize(const_cast<wxButton *>(this));
571 if ( m_imageData )
572 {
573 const wxSize sizeBmp = m_imageData->GetBitmap(State_Normal).GetSize();
574 const wxDirection dirBmp = m_imageData->GetBitmapPosition();
575 if ( dirBmp == wxLEFT || dirBmp == wxRIGHT )
576 {
577 size.x += sizeBmp.x;
578 if ( sizeBmp.y > size.y )
579 size.y = sizeBmp.y;
580 }
581 else // bitmap on top/below the text
582 {
583 size.y += sizeBmp.y;
584 if ( sizeBmp.x > size.x )
585 size.x = sizeBmp.x;
586 }
587
588 size += 2*m_imageData->GetBitmapMargins();
589
590 CacheBestSize(size);
591 }
592
593 return size;
594 }
595
596 /* static */
597 wxSize wxButtonBase::GetDefaultSize()
598 {
599 static wxSize s_sizeBtn;
600
601 if ( s_sizeBtn.x == 0 )
602 {
603 wxScreenDC dc;
604 dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT));
605
606 // the size of a standard button in the dialog units is 50x14,
607 // translate this to pixels
608 // NB1: the multipliers come from the Windows convention
609 // NB2: the extra +1/+2 were needed to get the size be the same as the
610 // size of the buttons in the standard dialog - I don't know how
611 // this happens, but on my system this size is 75x23 in pixels and
612 // 23*8 isn't even divisible by 14... Would be nice to understand
613 // why these constants are needed though!
614 s_sizeBtn.x = (50 * (dc.GetCharWidth() + 1))/4;
615 s_sizeBtn.y = ((14 * dc.GetCharHeight()) + 2)/8;
616 }
617
618 return s_sizeBtn;
619 }
620
621 // ----------------------------------------------------------------------------
622 // default button handling
623 // ----------------------------------------------------------------------------
624
625 /*
626 "Everything you ever wanted to know about the default buttons" or "Why do we
627 have to do all this?"
628
629 In MSW the default button should be activated when the user presses Enter
630 and the current control doesn't process Enter itself somehow. This is
631 handled by ::DefWindowProc() (or maybe ::DefDialogProc()) using DM_SETDEFID
632 Another aspect of "defaultness" is that the default button has different
633 appearance: this is due to BS_DEFPUSHBUTTON style which is completely
634 separate from DM_SETDEFID stuff (!). Also note that BS_DEFPUSHBUTTON should
635 be unset if our parent window is not active so it should be unset whenever
636 we lose activation and set back when we regain it.
637
638 Final complication is that when a button is active, it should be the default
639 one, i.e. pressing Enter on a button always activates it and not another
640 one.
641
642 We handle this by maintaining a permanent and a temporary default items in
643 wxControlContainer (both may be NULL). When a button becomes the current
644 control (i.e. gets focus) it sets itself as the temporary default which
645 ensures that it has the right appearance and that Enter will be redirected
646 to it. When the button loses focus, it unsets the temporary default and so
647 the default item will be the permanent default -- that is the default button
648 if any had been set or none otherwise, which is just what we want.
649
650 NB: all this is quite complicated by now and the worst is that normally
651 it shouldn't be necessary at all as for the normal Windows programs
652 DefWindowProc() and IsDialogMessage() take care of all this
653 automatically -- however in wxWidgets programs this doesn't work for
654 nested hierarchies (i.e. a notebook inside a notebook) for unknown
655 reason and so we have to reproduce all this code ourselves. It would be
656 very nice if we could avoid doing it.
657 */
658
659 // set this button as the (permanently) default one in its panel
660 wxWindow *wxButton::SetDefault()
661 {
662 // set this one as the default button both for wxWidgets ...
663 wxWindow *winOldDefault = wxButtonBase::SetDefault();
664
665 // ... and Windows
666 SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), false);
667 SetDefaultStyle(this, true);
668
669 return winOldDefault;
670 }
671
672 // return the top level parent window if it's not being deleted yet, otherwise
673 // return NULL
674 static wxTopLevelWindow *GetTLWParentIfNotBeingDeleted(wxWindow *win)
675 {
676 for ( ;; )
677 {
678 // IsTopLevel() will return false for a wxTLW being deleted, so we also
679 // need the parent test for this case
680 wxWindow * const parent = win->GetParent();
681 if ( !parent || win->IsTopLevel() )
682 {
683 if ( win->IsBeingDeleted() )
684 return NULL;
685
686 break;
687 }
688
689 win = parent;
690 }
691
692 wxASSERT_MSG( win, _T("button without top level parent?") );
693
694 wxTopLevelWindow * const tlw = wxDynamicCast(win, wxTopLevelWindow);
695 wxASSERT_MSG( tlw, _T("logic error in GetTLWParentIfNotBeingDeleted()") );
696
697 return tlw;
698 }
699
700 // set this button as being currently default
701 void wxButton::SetTmpDefault()
702 {
703 wxTopLevelWindow * const tlw = GetTLWParentIfNotBeingDeleted(GetParent());
704 if ( !tlw )
705 return;
706
707 wxWindow *winOldDefault = tlw->GetDefaultItem();
708 tlw->SetTmpDefaultItem(this);
709
710 SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), false);
711 SetDefaultStyle(this, true);
712 }
713
714 // unset this button as currently default, it may still stay permanent default
715 void wxButton::UnsetTmpDefault()
716 {
717 wxTopLevelWindow * const tlw = GetTLWParentIfNotBeingDeleted(GetParent());
718 if ( !tlw )
719 return;
720
721 tlw->SetTmpDefaultItem(NULL);
722
723 wxWindow *winOldDefault = tlw->GetDefaultItem();
724
725 SetDefaultStyle(this, false);
726 SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), true);
727 }
728
729 /* static */
730 void
731 wxButton::SetDefaultStyle(wxButton *btn, bool on)
732 {
733 // we may be called with NULL pointer -- simpler to do the check here than
734 // in the caller which does wxDynamicCast()
735 if ( !btn )
736 return;
737
738 // first, let DefDlgProc() know about the new default button
739 if ( on )
740 {
741 // we shouldn't set BS_DEFPUSHBUTTON for any button if we don't have
742 // focus at all any more
743 if ( !wxTheApp->IsActive() )
744 return;
745
746 wxWindow * const tlw = wxGetTopLevelParent(btn);
747 wxCHECK_RET( tlw, _T("button without top level window?") );
748
749 ::SendMessage(GetHwndOf(tlw), DM_SETDEFID, btn->GetId(), 0L);
750
751 // sending DM_SETDEFID also changes the button style to
752 // BS_DEFPUSHBUTTON so there is nothing more to do
753 }
754
755 // then also change the style as needed
756 long style = ::GetWindowLong(GetHwndOf(btn), GWL_STYLE);
757 if ( !(style & BS_DEFPUSHBUTTON) == on )
758 {
759 // don't do it with the owner drawn buttons because it will
760 // reset BS_OWNERDRAW style bit too (as BS_OWNERDRAW &
761 // BS_DEFPUSHBUTTON != 0)!
762 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
763 {
764 ::SendMessage(GetHwndOf(btn), BM_SETSTYLE,
765 on ? style | BS_DEFPUSHBUTTON
766 : style & ~BS_DEFPUSHBUTTON,
767 1L /* redraw */);
768 }
769 else // owner drawn
770 {
771 // redraw the button - it will notice itself that it's
772 // [not] the default one [any longer]
773 btn->Refresh();
774 }
775 }
776 //else: already has correct style
777 }
778
779 // ----------------------------------------------------------------------------
780 // helpers
781 // ----------------------------------------------------------------------------
782
783 bool wxButton::SendClickEvent()
784 {
785 wxCommandEvent event(wxEVT_COMMAND_BUTTON_CLICKED, GetId());
786 event.SetEventObject(this);
787
788 return ProcessCommand(event);
789 }
790
791 void wxButton::Command(wxCommandEvent & event)
792 {
793 ProcessCommand(event);
794 }
795
796 // ----------------------------------------------------------------------------
797 // event/message handlers
798 // ----------------------------------------------------------------------------
799
800 bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
801 {
802 bool processed = false;
803 switch ( param )
804 {
805 // NOTE: Apparently older versions (NT 4?) of the common controls send
806 // BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
807 // buttons, so in order to send two EVT_BUTTON events we should
808 // catch both types. Currently (Feb 2003) up-to-date versions of
809 // win98, win2k and winXP all send two BN_CLICKED messages for
810 // all button types, so we don't catch BN_DOUBLECLICKED anymore
811 // in order to not get 3 EVT_BUTTON events. If this is a problem
812 // then we need to figure out which version of the comctl32 changed
813 // this behaviour and test for it.
814
815 case 1: // message came from an accelerator
816 case BN_CLICKED: // normal buttons send this
817 processed = SendClickEvent();
818 break;
819 }
820
821 return processed;
822 }
823
824 WXLRESULT wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
825 {
826 // when we receive focus, we want to temporarily become the default button in
827 // our parent panel so that pressing "Enter" would activate us -- and when
828 // losing it we should restore the previous default button as well
829 if ( nMsg == WM_SETFOCUS )
830 {
831 SetTmpDefault();
832
833 // let the default processing take place too
834 }
835 else if ( nMsg == WM_KILLFOCUS )
836 {
837 UnsetTmpDefault();
838 }
839 else if ( nMsg == WM_LBUTTONDBLCLK )
840 {
841 // emulate a click event to force an owner-drawn button to change its
842 // appearance - without this, it won't do it
843 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN, wParam, lParam);
844
845 // and continue with processing the message normally as well
846 }
847 #if wxUSE_UXTHEME
848 else if ( nMsg == WM_THEMECHANGED )
849 {
850 // need to recalculate the best size here
851 // as the theme size might have changed
852 InvalidateBestSize();
853 }
854 else if ( wxUxThemeEngine::GetIfActive() )
855 {
856 // we need to Refresh() if mouse has entered or left window
857 // so we can update the hot tracking state
858 // must use m_mouseInWindow here instead of IsMouseInWindow()
859 // since we need to know the first time the mouse enters the window
860 // and IsMouseInWindow() would return true in this case
861 if ( ( nMsg == WM_MOUSEMOVE && !m_mouseInWindow ) ||
862 nMsg == WM_MOUSELEAVE )
863 {
864 Refresh();
865 }
866 }
867 #endif // wxUSE_UXTHEME
868
869 // let the base class do all real processing
870 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
871 }
872
873 // ----------------------------------------------------------------------------
874 // button images
875 // ----------------------------------------------------------------------------
876
877 wxBitmap wxButton::DoGetBitmap(State which) const
878 {
879 return m_imageData ? m_imageData->GetBitmap(which) : wxBitmap();
880 }
881
882 void wxButton::DoSetBitmap(const wxBitmap& bitmap, State which)
883 {
884 // allocate the image data when the first bitmap is set
885 if ( !m_imageData )
886 {
887 #if wxUSE_UXTHEME
888 if ( wxUxThemeEngine::GetIfActive() )
889 {
890 m_imageData = new wxXPButtonImageData(this, bitmap.GetSize());
891 }
892 else
893 #endif // wxUSE_UXTHEME
894 {
895 m_imageData = new wxODButtonImageData(this);
896 MakeOwnerDrawn();
897 }
898
899 // if a bitmap was assigned to the bitmap, its best size must be
900 // changed to account for it
901 InvalidateBestSize();
902 }
903
904 m_imageData->SetBitmap(bitmap, which);
905 }
906
907 void wxButton::DoSetBitmapMargins(wxCoord x, wxCoord y)
908 {
909 wxCHECK_RET( m_imageData, "SetBitmap() must be called first" );
910
911 m_imageData->SetBitmapMargins(x, y);
912 }
913
914 void wxButton::DoSetBitmapPosition(wxDirection dir)
915 {
916 wxCHECK_RET( m_imageData, "SetBitmap() must be called first" );
917
918 m_imageData->SetBitmapPosition(dir);
919 }
920
921 // ----------------------------------------------------------------------------
922 // owner-drawn buttons support
923 // ----------------------------------------------------------------------------
924
925 // drawing helpers
926 namespace
927 {
928
929 void DrawButtonText(HDC hdc,
930 RECT *pRect,
931 const wxString& text,
932 COLORREF col,
933 int flags)
934 {
935 wxTextColoursChanger changeFg(hdc, col, CLR_INVALID);
936 wxBkModeChanger changeBkMode(hdc, wxBRUSHSTYLE_TRANSPARENT);
937
938 // center text horizontally in any case
939 flags |= DT_CENTER;
940
941 if ( text.find(_T('\n')) != wxString::npos )
942 {
943 // draw multiline label
944
945 // first we need to compute its bounding rect
946 RECT rc;
947 ::CopyRect(&rc, pRect);
948 ::DrawText(hdc, text.wx_str(), text.length(), &rc,
949 DT_CENTER | DT_CALCRECT);
950
951 // now center this rect inside the entire button area
952 const LONG w = rc.right - rc.left;
953 const LONG h = rc.bottom - rc.top;
954 rc.left = (pRect->right - pRect->left)/2 - w/2;
955 rc.right = rc.left+w;
956 rc.top = (pRect->bottom - pRect->top)/2 - h/2;
957 rc.bottom = rc.top+h;
958
959 ::DrawText(hdc, text.wx_str(), text.length(), &rc, flags);
960 }
961 else // single line label
962 {
963 // centre text vertically too (notice that we must have DT_SINGLELINE
964 // for DT_VCENTER to work)
965 ::DrawText(hdc, text.wx_str(), text.length(), pRect,
966 flags | DT_SINGLELINE | DT_VCENTER);
967 }
968 }
969
970 void DrawRect(HDC hdc, const RECT& r)
971 {
972 wxDrawLine(hdc, r.left, r.top, r.right, r.top);
973 wxDrawLine(hdc, r.right, r.top, r.right, r.bottom);
974 wxDrawLine(hdc, r.right, r.bottom, r.left, r.bottom);
975 wxDrawLine(hdc, r.left, r.bottom, r.left, r.top);
976 }
977
978 /*
979 The button frame looks like this normally:
980
981 WWWWWWWWWWWWWWWWWWB
982 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
983 WH GB H = light grey (LIGHT)
984 WH GB G = dark grey (SHADOW)
985 WH GB B = black (DKSHADOW)
986 WH GB
987 WGGGGGGGGGGGGGGGGGB
988 BBBBBBBBBBBBBBBBBBB
989
990 When the button is selected, the button becomes like this (the total button
991 size doesn't change):
992
993 BBBBBBBBBBBBBBBBBBB
994 BWWWWWWWWWWWWWWWWBB
995 BWHHHHHHHHHHHHHHGBB
996 BWH GBB
997 BWH GBB
998 BWGGGGGGGGGGGGGGGBB
999 BBBBBBBBBBBBBBBBBBB
1000 BBBBBBBBBBBBBBBBBBB
1001
1002 When the button is pushed (while selected) it is like:
1003
1004 BBBBBBBBBBBBBBBBBBB
1005 BGGGGGGGGGGGGGGGGGB
1006 BG GB
1007 BG GB
1008 BG GB
1009 BG GB
1010 BGGGGGGGGGGGGGGGGGB
1011 BBBBBBBBBBBBBBBBBBB
1012 */
1013 void DrawButtonFrame(HDC hdc, RECT& rectBtn,
1014 bool selected, bool pushed)
1015 {
1016 RECT r;
1017 CopyRect(&r, &rectBtn);
1018
1019 AutoHPEN hpenBlack(GetSysColor(COLOR_3DDKSHADOW)),
1020 hpenGrey(GetSysColor(COLOR_3DSHADOW)),
1021 hpenLightGr(GetSysColor(COLOR_3DLIGHT)),
1022 hpenWhite(GetSysColor(COLOR_3DHILIGHT));
1023
1024 SelectInHDC selectPen(hdc, hpenBlack);
1025
1026 r.right--;
1027 r.bottom--;
1028
1029 if ( pushed )
1030 {
1031 DrawRect(hdc, r);
1032
1033 (void)SelectObject(hdc, hpenGrey);
1034 ::InflateRect(&r, -1, -1);
1035
1036 DrawRect(hdc, r);
1037 }
1038 else // !pushed
1039 {
1040 if ( selected )
1041 {
1042 DrawRect(hdc, r);
1043
1044 ::InflateRect(&r, -1, -1);
1045 }
1046
1047 wxDrawLine(hdc, r.left, r.bottom, r.right, r.bottom);
1048 wxDrawLine(hdc, r.right, r.bottom, r.right, r.top - 1);
1049
1050 (void)SelectObject(hdc, hpenWhite);
1051 wxDrawLine(hdc, r.left, r.bottom - 1, r.left, r.top);
1052 wxDrawLine(hdc, r.left, r.top, r.right, r.top);
1053
1054 (void)SelectObject(hdc, hpenLightGr);
1055 wxDrawLine(hdc, r.left + 1, r.bottom - 2, r.left + 1, r.top + 1);
1056 wxDrawLine(hdc, r.left + 1, r.top + 1, r.right - 1, r.top + 1);
1057
1058 (void)SelectObject(hdc, hpenGrey);
1059 wxDrawLine(hdc, r.left + 1, r.bottom - 1, r.right - 1, r.bottom - 1);
1060 wxDrawLine(hdc, r.right - 1, r.bottom - 1, r.right - 1, r.top);
1061 }
1062
1063 InflateRect(&rectBtn, -OD_BUTTON_MARGIN, -OD_BUTTON_MARGIN);
1064 }
1065
1066 #if wxUSE_UXTHEME
1067 void MSWDrawXPBackground(wxButton *button, HDC hdc, RECT& rectBtn, UINT state)
1068 {
1069 wxUxThemeHandle theme(button, L"BUTTON");
1070 int iState;
1071
1072 if ( state & ODS_SELECTED )
1073 {
1074 iState = PBS_PRESSED;
1075 }
1076 else if ( button->HasCapture() || button->IsMouseInWindow() )
1077 {
1078 iState = PBS_HOT;
1079 }
1080 else if ( state & ODS_FOCUS )
1081 {
1082 iState = PBS_DEFAULTED;
1083 }
1084 else if ( state & ODS_DISABLED )
1085 {
1086 iState = PBS_DISABLED;
1087 }
1088 else
1089 {
1090 iState = PBS_NORMAL;
1091 }
1092
1093 // draw parent background if needed
1094 if ( wxUxThemeEngine::Get()->IsThemeBackgroundPartiallyTransparent(theme,
1095 BP_PUSHBUTTON,
1096 iState) )
1097 {
1098 wxUxThemeEngine::Get()->DrawThemeParentBackground(GetHwndOf(button), hdc, &rectBtn);
1099 }
1100
1101 // draw background
1102 wxUxThemeEngine::Get()->DrawThemeBackground(theme, hdc, BP_PUSHBUTTON, iState,
1103 &rectBtn, NULL);
1104
1105 // calculate content area margins
1106 MARGINS margins;
1107 wxUxThemeEngine::Get()->GetThemeMargins(theme, hdc, BP_PUSHBUTTON, iState,
1108 TMT_CONTENTMARGINS, &rectBtn, &margins);
1109 ::InflateRect(&rectBtn, -margins.cxLeftWidth, -margins.cyTopHeight);
1110
1111 if ( button->UseBgCol() )
1112 {
1113 COLORREF colBg = wxColourToRGB(button->GetBackgroundColour());
1114 AutoHBRUSH hbrushBackground(colBg);
1115
1116 // don't overwrite the focus rect
1117 RECT rectClient;
1118 ::CopyRect(&rectClient, &rectBtn);
1119 ::InflateRect(&rectClient, -1, -1);
1120 FillRect(hdc, &rectClient, hbrushBackground);
1121 }
1122 }
1123 #endif // wxUSE_UXTHEME
1124
1125 } // anonymous namespace
1126
1127 // ----------------------------------------------------------------------------
1128 // owner drawn buttons support
1129 // ----------------------------------------------------------------------------
1130
1131 void wxButton::MakeOwnerDrawn()
1132 {
1133 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
1134 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
1135 {
1136 // make it so
1137 style |= BS_OWNERDRAW;
1138 SetWindowLong(GetHwnd(), GWL_STYLE, style);
1139 }
1140 }
1141
1142 bool wxButton::SetBackgroundColour(const wxColour &colour)
1143 {
1144 if ( !wxControl::SetBackgroundColour(colour) )
1145 {
1146 // nothing to do
1147 return false;
1148 }
1149
1150 MakeOwnerDrawn();
1151
1152 Refresh();
1153
1154 return true;
1155 }
1156
1157 bool wxButton::SetForegroundColour(const wxColour &colour)
1158 {
1159 if ( !wxControl::SetForegroundColour(colour) )
1160 {
1161 // nothing to do
1162 return false;
1163 }
1164
1165 MakeOwnerDrawn();
1166
1167 Refresh();
1168
1169 return true;
1170 }
1171
1172 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
1173 {
1174 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
1175 HDC hdc = lpDIS->hDC;
1176
1177 UINT state = lpDIS->itemState;
1178 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
1179
1180 RECT rectBtn;
1181 CopyRect(&rectBtn, &lpDIS->rcItem);
1182
1183 // draw the button background
1184 #if wxUSE_UXTHEME
1185 if ( wxUxThemeEngine::GetIfActive() )
1186 {
1187 MSWDrawXPBackground(this, hdc, rectBtn, state);
1188 }
1189 else
1190 #endif // wxUSE_UXTHEME
1191 {
1192 COLORREF colBg = wxColourToRGB(GetBackgroundColour());
1193
1194 // first, draw the background
1195 AutoHBRUSH hbrushBackground(colBg);
1196 FillRect(hdc, &rectBtn, hbrushBackground);
1197
1198 // draw the border for the current state
1199 bool selected = (state & ODS_SELECTED) != 0;
1200 if ( !selected )
1201 {
1202 wxTopLevelWindow *
1203 tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
1204 if ( tlw )
1205 {
1206 selected = tlw->GetDefaultItem() == this;
1207 }
1208 }
1209
1210 DrawButtonFrame(hdc, rectBtn, selected, pushed);
1211 }
1212
1213 // draw the focus rectangle if we need it
1214 if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) )
1215 {
1216 DrawFocusRect(hdc, &rectBtn);
1217
1218 #if wxUSE_UXTHEME
1219 if ( !wxUxThemeEngine::GetIfActive() )
1220 #endif // wxUSE_UXTHEME
1221 {
1222 if ( pushed )
1223 {
1224 // the label is shifted by 1 pixel to create "pushed" effect
1225 OffsetRect(&rectBtn, 1, 1);
1226 }
1227 }
1228 }
1229
1230
1231 // draw the image, if any
1232 if ( m_imageData )
1233 {
1234 wxBitmap bmp = m_imageData->GetBitmap(State_Normal);
1235 const wxSize sizeBmp = bmp.GetSize();
1236 const wxSize margin = m_imageData->GetBitmapMargins();
1237 const wxSize sizeBmpWithMargins(sizeBmp + 2*margin);
1238 wxRect rectButton(wxRectFromRECT(rectBtn));
1239
1240 // for simplicity, we start with centred rectangle and then move it to
1241 // the appropriate edge
1242 wxRect rectBitmap = wxRect(sizeBmp).CentreIn(rectButton);
1243 switch ( m_imageData->GetBitmapPosition() )
1244 {
1245 default:
1246 wxFAIL_MSG( "invalid direction" );
1247 // fall through
1248
1249 case wxLEFT:
1250 rectBitmap.x = rectButton.x + margin.x;
1251 rectButton.x += sizeBmpWithMargins.x;
1252 rectButton.width -= sizeBmpWithMargins.x;
1253 break;
1254
1255 case wxRIGHT:
1256 rectBitmap.x = rectButton.GetRight() - sizeBmp.x - margin.x;
1257 rectButton.width -= sizeBmpWithMargins.x;
1258 break;
1259
1260 case wxTOP:
1261 rectBitmap.y = rectButton.y + margin.y;
1262 rectButton.y += sizeBmpWithMargins.y;
1263 rectButton.height -= sizeBmpWithMargins.y;
1264 break;
1265
1266 case wxBOTTOM:
1267 rectBitmap.y = rectButton.GetBottom() - sizeBmp.y - margin.y;
1268 rectButton.height -= sizeBmpWithMargins.y;
1269 break;
1270 }
1271
1272 wxDCTemp dst((WXHDC)hdc);
1273 dst.DrawBitmap(bmp, rectBitmap.GetPosition(), true);
1274
1275 wxCopyRectToRECT(rectButton, rectBtn);
1276 }
1277
1278
1279 // finally draw the label
1280 COLORREF colFg = state & ODS_DISABLED
1281 ? ::GetSysColor(COLOR_GRAYTEXT)
1282 : wxColourToRGB(GetForegroundColour());
1283
1284 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000) systems
1285 // but by happy coincidence ODS_NOACCEL is not used under them neither so
1286 // DT_HIDEPREFIX should never be used there
1287 DrawButtonText(hdc, &rectBtn, GetLabel(), colFg,
1288 state & ODS_NOACCEL ? DT_HIDEPREFIX : 0);
1289
1290 return true;
1291 }
1292
1293 #endif // wxUSE_BUTTON
1294