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