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