no real changes, just extract private classes from msw/dc.cpp into a private header...
[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 HPEN hpenBlack = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW)),
987 hpenGrey = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW)),
988 hpenLightGr = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT)),
989 hpenWhite = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT));
990
991 HPEN hpenOld = (HPEN)SelectObject(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 (void)SelectObject(hdc, hpenOld);
1031 DeleteObject(hpenWhite);
1032 DeleteObject(hpenLightGr);
1033 DeleteObject(hpenGrey);
1034 DeleteObject(hpenBlack);
1035 }
1036
1037 #if wxUSE_UXTHEME
1038 void MSWDrawXPBackground(wxButton *button, WXDRAWITEMSTRUCT *wxdis)
1039 {
1040 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
1041 HDC hdc = lpDIS->hDC;
1042 UINT state = lpDIS->itemState;
1043 RECT rectBtn;
1044 CopyRect(&rectBtn, &lpDIS->rcItem);
1045
1046 wxUxThemeHandle theme(button, L"BUTTON");
1047 int iState;
1048
1049 if ( state & ODS_SELECTED )
1050 {
1051 iState = PBS_PRESSED;
1052 }
1053 else if ( button->HasCapture() || button->IsMouseInWindow() )
1054 {
1055 iState = PBS_HOT;
1056 }
1057 else if ( state & ODS_FOCUS )
1058 {
1059 iState = PBS_DEFAULTED;
1060 }
1061 else if ( state & ODS_DISABLED )
1062 {
1063 iState = PBS_DISABLED;
1064 }
1065 else
1066 {
1067 iState = PBS_NORMAL;
1068 }
1069
1070 // draw parent background if needed
1071 if ( wxUxThemeEngine::Get()->IsThemeBackgroundPartiallyTransparent(theme,
1072 BP_PUSHBUTTON,
1073 iState) )
1074 {
1075 wxUxThemeEngine::Get()->DrawThemeParentBackground(GetHwndOf(button), hdc, &rectBtn);
1076 }
1077
1078 // draw background
1079 wxUxThemeEngine::Get()->DrawThemeBackground(theme, hdc, BP_PUSHBUTTON, iState,
1080 &rectBtn, NULL);
1081
1082 // calculate content area margins
1083 MARGINS margins;
1084 wxUxThemeEngine::Get()->GetThemeMargins(theme, hdc, BP_PUSHBUTTON, iState,
1085 TMT_CONTENTMARGINS, &rectBtn, &margins);
1086 RECT rectClient;
1087 ::CopyRect(&rectClient, &rectBtn);
1088 ::InflateRect(&rectClient, -margins.cxLeftWidth, -margins.cyTopHeight);
1089
1090 // if focused and !nofocus rect
1091 if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) )
1092 {
1093 DrawFocusRect(hdc, &rectClient);
1094 }
1095
1096 if ( button->UseBgCol() )
1097 {
1098 COLORREF colBg = wxColourToRGB(button->GetBackgroundColour());
1099 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
1100
1101 // don't overwrite the focus rect
1102 ::InflateRect(&rectClient, -1, -1);
1103 FillRect(hdc, &rectClient, hbrushBackground);
1104 ::DeleteObject(hbrushBackground);
1105 }
1106 }
1107 #endif // wxUSE_UXTHEME
1108
1109 } // anonymous namespace
1110
1111 // ----------------------------------------------------------------------------
1112 // owner drawn buttons support
1113 // ----------------------------------------------------------------------------
1114
1115 void wxButton::MakeOwnerDrawn()
1116 {
1117 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
1118 if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW )
1119 {
1120 // make it so
1121 style |= BS_OWNERDRAW;
1122 SetWindowLong(GetHwnd(), GWL_STYLE, style);
1123 }
1124 }
1125
1126 bool wxButton::SetBackgroundColour(const wxColour &colour)
1127 {
1128 if ( !wxControl::SetBackgroundColour(colour) )
1129 {
1130 // nothing to do
1131 return false;
1132 }
1133
1134 MakeOwnerDrawn();
1135
1136 Refresh();
1137
1138 return true;
1139 }
1140
1141 bool wxButton::SetForegroundColour(const wxColour &colour)
1142 {
1143 if ( !wxControl::SetForegroundColour(colour) )
1144 {
1145 // nothing to do
1146 return false;
1147 }
1148
1149 MakeOwnerDrawn();
1150
1151 Refresh();
1152
1153 return true;
1154 }
1155
1156 bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
1157 {
1158 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
1159 HDC hdc = lpDIS->hDC;
1160 UINT state = lpDIS->itemState;
1161 RECT rectBtn;
1162 CopyRect(&rectBtn, &lpDIS->rcItem);
1163
1164 #if wxUSE_UXTHEME
1165 if ( wxUxThemeEngine::GetIfActive() )
1166 {
1167 MSWDrawXPBackground(this, wxdis);
1168 }
1169 else
1170 #endif // wxUSE_UXTHEME
1171 {
1172 COLORREF colBg = wxColourToRGB(GetBackgroundColour());
1173
1174 // first, draw the background
1175 HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
1176 FillRect(hdc, &rectBtn, hbrushBackground);
1177 ::DeleteObject(hbrushBackground);
1178
1179 // draw the border for the current state
1180 bool selected = (state & ODS_SELECTED) != 0;
1181 if ( !selected )
1182 {
1183 wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
1184 if ( tlw )
1185 {
1186 selected = tlw->GetDefaultItem() == this;
1187 }
1188 }
1189 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
1190
1191 DrawButtonFrame(hdc, rectBtn, selected, pushed);
1192
1193 // if focused and !nofocus rect
1194 if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) )
1195 {
1196 RECT rectFocus;
1197 CopyRect(&rectFocus, &rectBtn);
1198
1199 // I don't know where does this constant come from, but this is how
1200 // Windows draws them
1201 InflateRect(&rectFocus, -4, -4);
1202
1203 DrawFocusRect(hdc, &rectFocus);
1204 }
1205
1206 if ( pushed )
1207 {
1208 // the label is shifted by 1 pixel to create "pushed" effect
1209 OffsetRect(&rectBtn, 1, 1);
1210 }
1211 }
1212
1213 COLORREF colFg = state & ODS_DISABLED
1214 ? ::GetSysColor(COLOR_GRAYTEXT)
1215 : wxColourToRGB(GetForegroundColour());
1216
1217 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000) systems
1218 // but by happy coincidence ODS_NOACCEL is not used under them neither so
1219 // DT_HIDEPREFIX should never be used there
1220 DrawButtonText(hdc, &rectBtn, GetLabel(), colFg,
1221 state & ODS_NOACCEL ? DT_HIDEPREFIX : 0);
1222
1223 return true;
1224 }
1225
1226 #endif // wxUSE_BUTTON
1227