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