Merge in from trunk r68684 - r69046
[wxWidgets.git] / src / msw / anybutton.cpp
1 /////////////////////////////////////////////////////////////////////////////
2 // Name: src/msw/anybutton.cpp
3 // Purpose: wxAnyButton
4 // Author: Julian Smart
5 // Created: 1998-01-04 (extracted from button.cpp)
6 // RCS-ID: $Id$
7 // Copyright: (c) Julian Smart
8 // Licence: wxWindows licence
9 /////////////////////////////////////////////////////////////////////////////
10
11 // ============================================================================
12 // declarations
13 // ============================================================================
14
15 // ----------------------------------------------------------------------------
16 // headers
17 // ----------------------------------------------------------------------------
18
19 // For compilers that support precompilation, includes "wx.h".
20 #include "wx/wxprec.h"
21
22 #ifdef __BORLANDC__
23 #pragma hdrstop
24 #endif
25
26 #ifdef wxHAS_ANY_BUTTON
27
28 #include "wx/anybutton.h"
29
30 #ifndef WX_PRECOMP
31 #include "wx/app.h"
32 #include "wx/brush.h"
33 #include "wx/panel.h"
34 #include "wx/bmpbuttn.h"
35 #include "wx/settings.h"
36 #include "wx/dcscreen.h"
37 #include "wx/dcclient.h"
38 #include "wx/toplevel.h"
39 #include "wx/msw/wrapcctl.h"
40 #include "wx/msw/private.h"
41 #include "wx/msw/missing.h"
42 #endif
43
44 #include "wx/imaglist.h"
45 #include "wx/stockitem.h"
46 #include "wx/msw/private/button.h"
47 #include "wx/msw/private/dc.h"
48 #include "wx/private/window.h"
49
50 #if wxUSE_MARKUP
51 #include "wx/generic/private/markuptext.h"
52 #endif // wxUSE_MARKUP
53
54 using namespace wxMSWImpl;
55
56 #if wxUSE_UXTHEME
57 #include "wx/msw/uxtheme.h"
58
59 // no need to include tmschema.h
60 #ifndef BP_PUSHBUTTON
61 #define BP_PUSHBUTTON 1
62
63 #define PBS_NORMAL 1
64 #define PBS_HOT 2
65 #define PBS_PRESSED 3
66 #define PBS_DISABLED 4
67 #define PBS_DEFAULTED 5
68
69 #define TMT_CONTENTMARGINS 3602
70 #endif
71
72 // provide the necessary declarations ourselves if they're missing from
73 // headers
74 #ifndef BCM_SETIMAGELIST
75 #define BCM_SETIMAGELIST 0x1602
76 #define BCM_SETTEXTMARGIN 0x1604
77
78 enum
79 {
80 BUTTON_IMAGELIST_ALIGN_LEFT,
81 BUTTON_IMAGELIST_ALIGN_RIGHT,
82 BUTTON_IMAGELIST_ALIGN_TOP,
83 BUTTON_IMAGELIST_ALIGN_BOTTOM
84 };
85
86 struct BUTTON_IMAGELIST
87 {
88 HIMAGELIST himl;
89 RECT margin;
90 UINT uAlign;
91 };
92 #endif
93 #endif // wxUSE_UXTHEME
94
95 #ifndef WM_THEMECHANGED
96 #define WM_THEMECHANGED 0x031A
97 #endif
98
99 #ifndef ODS_NOACCEL
100 #define ODS_NOACCEL 0x0100
101 #endif
102
103 #ifndef ODS_NOFOCUSRECT
104 #define ODS_NOFOCUSRECT 0x0200
105 #endif
106
107 #ifndef DT_HIDEPREFIX
108 #define DT_HIDEPREFIX 0x00100000
109 #endif
110
111 #if wxUSE_UXTHEME
112 extern wxWindowMSW *wxWindowBeingErased; // From src/msw/window.cpp
113 #endif // wxUSE_UXTHEME
114
115 // ----------------------------------------------------------------------------
116 // button image data
117 // ----------------------------------------------------------------------------
118
119 // we use different data classes for owner drawn buttons and for themed XP ones
120
121 class wxButtonImageData
122 {
123 public:
124 wxButtonImageData() { }
125 virtual ~wxButtonImageData() { }
126
127 virtual wxBitmap GetBitmap(wxAnyButton::State which) const = 0;
128 virtual void SetBitmap(const wxBitmap& bitmap, wxAnyButton::State which) = 0;
129
130 virtual wxSize GetBitmapMargins() const = 0;
131 virtual void SetBitmapMargins(wxCoord x, wxCoord y) = 0;
132
133 virtual wxDirection GetBitmapPosition() const = 0;
134 virtual void SetBitmapPosition(wxDirection dir) = 0;
135
136 private:
137 wxDECLARE_NO_COPY_CLASS(wxButtonImageData);
138 };
139
140 namespace
141 {
142
143 // the gap between button edge and the interior area used by Windows for the
144 // standard buttons
145 const int OD_BUTTON_MARGIN = 4;
146
147 class wxODButtonImageData : public wxButtonImageData
148 {
149 public:
150 wxODButtonImageData(wxAnyButton *btn, const wxBitmap& bitmap)
151 {
152 SetBitmap(bitmap, wxAnyButton::State_Normal);
153 SetBitmap(bitmap.ConvertToDisabled(), wxAnyButton::State_Disabled);
154
155 m_dir = wxLEFT;
156
157 // we use margins when we have both bitmap and text, but when we have
158 // only the bitmap it should take up the entire button area
159 if ( btn->ShowsLabel() )
160 {
161 m_margin.x = btn->GetCharWidth();
162 m_margin.y = btn->GetCharHeight() / 2;
163 }
164 }
165
166 virtual wxBitmap GetBitmap(wxAnyButton::State which) const
167 {
168 return m_bitmaps[which];
169 }
170
171 virtual void SetBitmap(const wxBitmap& bitmap, wxAnyButton::State which)
172 {
173 m_bitmaps[which] = bitmap;
174 }
175
176 virtual wxSize GetBitmapMargins() const
177 {
178 return m_margin;
179 }
180
181 virtual void SetBitmapMargins(wxCoord x, wxCoord y)
182 {
183 m_margin = wxSize(x, y);
184 }
185
186 virtual wxDirection GetBitmapPosition() const
187 {
188 return m_dir;
189 }
190
191 virtual void SetBitmapPosition(wxDirection dir)
192 {
193 m_dir = dir;
194 }
195
196 private:
197 // just store the values passed to us to be able to retrieve them later
198 // from the drawing code
199 wxBitmap m_bitmaps[wxAnyButton::State_Max];
200 wxSize m_margin;
201 wxDirection m_dir;
202
203 wxDECLARE_NO_COPY_CLASS(wxODButtonImageData);
204 };
205
206 #if wxUSE_UXTHEME
207
208 // somehow the margin is one pixel greater than the value returned by
209 // GetThemeMargins() call
210 const int XP_BUTTON_EXTRA_MARGIN = 1;
211
212 class wxXPButtonImageData : public wxButtonImageData
213 {
214 public:
215 // we must be constructed with the size of our images as we need to create
216 // the image list
217 wxXPButtonImageData(wxAnyButton *btn, const wxBitmap& bitmap)
218 : m_iml(bitmap.GetWidth(), bitmap.GetHeight(), true /* use mask */,
219 wxAnyButton::State_Max),
220 m_hwndBtn(GetHwndOf(btn))
221 {
222 // initialize all bitmaps except for the disabled one to normal state
223 for ( int n = 0; n < wxAnyButton::State_Max; n++ )
224 {
225 m_iml.Add(n == wxAnyButton::State_Disabled ? bitmap.ConvertToDisabled()
226 : bitmap);
227 }
228
229 m_data.himl = GetHimagelistOf(&m_iml);
230
231 // no margins by default
232 m_data.margin.left =
233 m_data.margin.right =
234 m_data.margin.top =
235 m_data.margin.bottom = 0;
236
237 // use default alignment
238 m_data.uAlign = BUTTON_IMAGELIST_ALIGN_LEFT;
239
240 UpdateImageInfo();
241 }
242
243 virtual wxBitmap GetBitmap(wxAnyButton::State which) const
244 {
245 return m_iml.GetBitmap(which);
246 }
247
248 virtual void SetBitmap(const wxBitmap& bitmap, wxAnyButton::State which)
249 {
250 m_iml.Replace(which, bitmap);
251
252 UpdateImageInfo();
253 }
254
255 virtual wxSize GetBitmapMargins() const
256 {
257 return wxSize(m_data.margin.left, m_data.margin.top);
258 }
259
260 virtual void SetBitmapMargins(wxCoord x, wxCoord y)
261 {
262 RECT& margin = m_data.margin;
263 margin.left =
264 margin.right = x;
265 margin.top =
266 margin.bottom = y;
267
268 if ( !::SendMessage(m_hwndBtn, BCM_SETTEXTMARGIN, 0, (LPARAM)&margin) )
269 {
270 wxLogDebug("SendMessage(BCM_SETTEXTMARGIN) failed");
271 }
272 }
273
274 virtual wxDirection GetBitmapPosition() const
275 {
276 switch ( m_data.uAlign )
277 {
278 default:
279 wxFAIL_MSG( "invalid image alignment" );
280 // fall through
281
282 case BUTTON_IMAGELIST_ALIGN_LEFT:
283 return wxLEFT;
284
285 case BUTTON_IMAGELIST_ALIGN_RIGHT:
286 return wxRIGHT;
287
288 case BUTTON_IMAGELIST_ALIGN_TOP:
289 return wxTOP;
290
291 case BUTTON_IMAGELIST_ALIGN_BOTTOM:
292 return wxBOTTOM;
293 }
294 }
295
296 virtual void SetBitmapPosition(wxDirection dir)
297 {
298 UINT alignNew;
299 switch ( dir )
300 {
301 default:
302 wxFAIL_MSG( "invalid direction" );
303 // fall through
304
305 case wxLEFT:
306 alignNew = BUTTON_IMAGELIST_ALIGN_LEFT;
307 break;
308
309 case wxRIGHT:
310 alignNew = BUTTON_IMAGELIST_ALIGN_RIGHT;
311 break;
312
313 case wxTOP:
314 alignNew = BUTTON_IMAGELIST_ALIGN_TOP;
315 break;
316
317 case wxBOTTOM:
318 alignNew = BUTTON_IMAGELIST_ALIGN_BOTTOM;
319 break;
320 }
321
322 if ( alignNew != m_data.uAlign )
323 {
324 m_data.uAlign = alignNew;
325 UpdateImageInfo();
326 }
327 }
328
329 private:
330 void UpdateImageInfo()
331 {
332 if ( !::SendMessage(m_hwndBtn, BCM_SETIMAGELIST, 0, (LPARAM)&m_data) )
333 {
334 wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed");
335 }
336 }
337
338 // we store image list separately to be able to use convenient wxImageList
339 // methods instead of working with raw HIMAGELIST
340 wxImageList m_iml;
341
342 // store the rest of the data in BCM_SETIMAGELIST-friendly form
343 BUTTON_IMAGELIST m_data;
344
345 // the button we're associated with
346 const HWND m_hwndBtn;
347
348
349 wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData);
350 };
351
352 #endif // wxUSE_UXTHEME
353
354 } // anonymous namespace
355
356 // ----------------------------------------------------------------------------
357 // macros
358 // ----------------------------------------------------------------------------
359
360 // ============================================================================
361 // implementation
362 // ============================================================================
363
364 // ----------------------------------------------------------------------------
365 // helper functions from wx/msw/private/button.h
366 // ----------------------------------------------------------------------------
367
368 void wxMSWButton::UpdateMultilineStyle(HWND hwnd, const wxString& label)
369 {
370 // update BS_MULTILINE style depending on the new label (resetting it
371 // doesn't seem to do anything very useful but it shouldn't hurt and we do
372 // have to set it whenever the label becomes multi line as otherwise it
373 // wouldn't be shown correctly as we don't use BS_MULTILINE when creating
374 // the control unless it already has new lines in its label)
375 long styleOld = ::GetWindowLong(hwnd, GWL_STYLE),
376 styleNew;
377 if ( label.find(wxT('\n')) != wxString::npos )
378 styleNew = styleOld | BS_MULTILINE;
379 else
380 styleNew = styleOld & ~BS_MULTILINE;
381
382 if ( styleNew != styleOld )
383 ::SetWindowLong(hwnd, GWL_STYLE, styleNew);
384 }
385
386 wxSize wxMSWButton::GetFittingSize(wxWindow *win,
387 const wxSize& sizeLabel,
388 int flags)
389 {
390 wxSize sizeBtn = sizeLabel;
391
392 // FIXME: The numbers here are pure guesswork, no idea how should the
393 // button margins be really calculated.
394 if ( flags & Size_ExactFit )
395 {
396 // We still need some margin or the text would be overwritten, just
397 // make it as small as possible.
398 sizeBtn.x += (3*win->GetCharWidth())/2;
399 }
400 else
401 {
402 sizeBtn.x += 3*win->GetCharWidth();
403 sizeBtn.y += win->GetCharHeight()/2;
404 }
405
406 // account for the shield UAC icon if we have it
407 if ( flags & Size_AuthNeeded )
408 sizeBtn.x += wxSystemSettings::GetMetric(wxSYS_SMALLICON_X);
409
410 return sizeBtn;
411 }
412
413 wxSize wxMSWButton::ComputeBestFittingSize(wxControl *btn, int flags)
414 {
415 wxClientDC dc(btn);
416
417 wxSize sizeBtn;
418 dc.GetMultiLineTextExtent(btn->GetLabelText(), &sizeBtn.x, &sizeBtn.y);
419
420 return GetFittingSize(btn, sizeBtn, flags);
421 }
422
423 wxSize wxMSWButton::IncreaseToStdSizeAndCache(wxControl *btn, const wxSize& size)
424 {
425 wxSize sizeBtn(size);
426
427 // All buttons have at least the standard height and, unless the user
428 // explicitly wants them to be as small as possible and used wxBU_EXACTFIT
429 // style to indicate this, of at least the standard width too.
430 //
431 // Notice that we really want to make all buttons equally high, otherwise
432 // they look ugly and the existing code using wxBU_EXACTFIT only uses it to
433 // control width and not height.
434
435 // The 50x14 button size is documented in the "Recommended sizing and
436 // spacing" section of MSDN layout article.
437 //
438 // Note that we intentionally don't use GetDefaultSize() here, because
439 // it's inexact -- dialog units depend on this dialog's font.
440 const wxSize sizeDef = btn->ConvertDialogToPixels(wxSize(50, 14));
441 if ( !btn->HasFlag(wxBU_EXACTFIT) )
442 {
443 if ( sizeBtn.x < sizeDef.x )
444 sizeBtn.x = sizeDef.x;
445 }
446 if ( sizeBtn.y < sizeDef.y )
447 sizeBtn.y = sizeDef.y;
448
449 btn->CacheBestSize(sizeBtn);
450
451 return sizeBtn;
452 }
453
454 // ----------------------------------------------------------------------------
455 // creation/destruction
456 // ----------------------------------------------------------------------------
457
458 wxAnyButton::~wxAnyButton()
459 {
460 delete m_imageData;
461 #if wxUSE_MARKUP
462 delete m_markupText;
463 #endif // wxUSE_MARKUP
464 }
465
466 void wxAnyButton::SetLabel(const wxString& label)
467 {
468 wxMSWButton::UpdateMultilineStyle(GetHwnd(), label);
469
470 wxAnyButtonBase::SetLabel(label);
471
472 #if wxUSE_MARKUP
473 // If we have a plain text label, we shouldn't be using markup any longer.
474 if ( m_markupText )
475 {
476 delete m_markupText;
477 m_markupText = NULL;
478
479 // Unfortunately we don't really know whether we can reset the button
480 // to be non-owner-drawn or not: if we had made it owner-drawn just
481 // because of a call to SetLabelMarkup(), we could, but not if there
482 // were [also] calls to Set{Fore,Back}groundColour(). If it's really a
483 // problem to have button remain owner-drawn forever just because it
484 // had markup label once, we should record the reason for our current
485 // owner-drawnness and check it here.
486 }
487 #endif // wxUSE_MARKUP
488 }
489
490 // ----------------------------------------------------------------------------
491 // size management including autosizing
492 // ----------------------------------------------------------------------------
493
494 void wxAnyButton::AdjustForBitmapSize(wxSize &size) const
495 {
496 wxCHECK_RET( m_imageData, wxT("shouldn't be called if no image") );
497
498 // account for the bitmap size
499 const wxSize sizeBmp = m_imageData->GetBitmap(State_Normal).GetSize();
500 const wxDirection dirBmp = m_imageData->GetBitmapPosition();
501 if ( dirBmp == wxLEFT || dirBmp == wxRIGHT )
502 {
503 size.x += sizeBmp.x;
504 if ( sizeBmp.y > size.y )
505 size.y = sizeBmp.y;
506 }
507 else // bitmap on top/below the text
508 {
509 size.y += sizeBmp.y;
510 if ( sizeBmp.x > size.x )
511 size.x = sizeBmp.x;
512 }
513
514 // account for the user-specified margins
515 size += 2*m_imageData->GetBitmapMargins();
516
517 // and also for the margins we always add internally (unless we have no
518 // border at all in which case the button has exactly the same size as
519 // bitmap and so no margins should be used)
520 if ( !HasFlag(wxBORDER_NONE) )
521 {
522 int marginH = 0,
523 marginV = 0;
524 #if wxUSE_UXTHEME
525 if ( wxUxThemeEngine::GetIfActive() )
526 {
527 wxUxThemeHandle theme(const_cast<wxAnyButton *>(this), L"BUTTON");
528
529 MARGINS margins;
530 wxUxThemeEngine::Get()->GetThemeMargins(theme, NULL,
531 BP_PUSHBUTTON,
532 PBS_NORMAL,
533 TMT_CONTENTMARGINS,
534 NULL,
535 &margins);
536
537 // XP doesn't draw themed buttons correctly when the client
538 // area is smaller than 8x8 - enforce this minimum size for
539 // small bitmaps
540 size.IncTo(wxSize(8, 8));
541
542 marginH = margins.cxLeftWidth + margins.cxRightWidth
543 + 2*XP_BUTTON_EXTRA_MARGIN;
544 marginV = margins.cyTopHeight + margins.cyBottomHeight
545 + 2*XP_BUTTON_EXTRA_MARGIN;
546 }
547 else
548 #endif // wxUSE_UXTHEME
549 {
550 marginH =
551 marginV = OD_BUTTON_MARGIN;
552 }
553
554 size.IncBy(marginH, marginV);
555 }
556 }
557
558 wxSize wxAnyButton::DoGetBestSize() const
559 {
560 wxAnyButton * const self = const_cast<wxAnyButton *>(this);
561
562 wxSize size;
563
564 // Account for the text part if we have it.
565 if ( ShowsLabel() )
566 {
567 int flags = 0;
568 if ( HasFlag(wxBU_EXACTFIT) )
569 flags |= wxMSWButton::Size_ExactFit;
570 if ( DoGetAuthNeeded() )
571 flags |= wxMSWButton::Size_AuthNeeded;
572
573 #if wxUSE_MARKUP
574 if ( m_markupText )
575 {
576 wxClientDC dc(self);
577 size = wxMSWButton::GetFittingSize(self,
578 m_markupText->Measure(dc),
579 flags);
580 }
581 else // Normal plain text (but possibly multiline) label.
582 #endif // wxUSE_MARKUP
583 {
584 size = wxMSWButton::ComputeBestFittingSize(self, flags);
585 }
586 }
587
588 if ( m_imageData )
589 AdjustForBitmapSize(size);
590
591 return wxMSWButton::IncreaseToStdSizeAndCache(self, size);
592 }
593
594 // ----------------------------------------------------------------------------
595 // event/message handlers
596 // ----------------------------------------------------------------------------
597
598 WXLRESULT wxAnyButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
599 {
600 if ( nMsg == WM_LBUTTONDBLCLK )
601 {
602 // emulate a click event to force an owner-drawn button to change its
603 // appearance - without this, it won't do it
604 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN, wParam, lParam);
605
606 // and continue with processing the message normally as well
607 }
608 #if wxUSE_UXTHEME
609 else if ( nMsg == WM_THEMECHANGED )
610 {
611 // need to recalculate the best size here
612 // as the theme size might have changed
613 InvalidateBestSize();
614 }
615 #endif // wxUSE_UXTHEME
616 // must use m_mouseInWindow here instead of IsMouseInWindow()
617 // since we need to know the first time the mouse enters the window
618 // and IsMouseInWindow() would return true in this case
619 else if ( (nMsg == WM_MOUSEMOVE && !m_mouseInWindow) ||
620 nMsg == WM_MOUSELEAVE )
621 {
622 if (
623 IsEnabled() &&
624 (
625 #if wxUSE_UXTHEME
626 wxUxThemeEngine::GetIfActive() ||
627 #endif // wxUSE_UXTHEME
628 (m_imageData && m_imageData->GetBitmap(State_Current).IsOk())
629 )
630 )
631 {
632 Refresh();
633 }
634 }
635
636 // let the base class do all real processing
637 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
638 }
639
640 // ----------------------------------------------------------------------------
641 // button images
642 // ----------------------------------------------------------------------------
643
644 wxBitmap wxAnyButton::DoGetBitmap(State which) const
645 {
646 return m_imageData ? m_imageData->GetBitmap(which) : wxBitmap();
647 }
648
649 void wxAnyButton::DoSetBitmap(const wxBitmap& bitmap, State which)
650 {
651 #if wxUSE_UXTHEME
652 wxXPButtonImageData *oldData = NULL;
653 #endif // wxUSE_UXTHEME
654
655 // Check if we already had bitmaps of different size.
656 if ( m_imageData &&
657 bitmap.GetSize() != m_imageData->GetBitmap(State_Normal).GetSize() )
658 {
659 wxASSERT_MSG( which == State_Normal,
660 "Must set normal bitmap with the new size first" );
661
662 #if wxUSE_UXTHEME
663 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
664 {
665 // We can't change the size of the images stored in wxImageList
666 // in wxXPButtonImageData::m_iml so force recreating it below but
667 // keep the current data to copy its values into the new one.
668 oldData = static_cast<wxXPButtonImageData *>(m_imageData);
669 m_imageData = NULL;
670 }
671 #endif // wxUSE_UXTHEME
672 //else: wxODButtonImageData doesn't require anything special
673 }
674
675 // allocate the image data when the first bitmap is set
676 if ( !m_imageData )
677 {
678 #if wxUSE_UXTHEME
679 // using image list doesn't work correctly if we don't have any label
680 // (even if we use BUTTON_IMAGELIST_ALIGN_CENTER alignment and
681 // BS_BITMAP style), at least under Windows 2003 so use owner drawn
682 // strategy for bitmap-only buttons
683 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
684 {
685 m_imageData = new wxXPButtonImageData(this, bitmap);
686
687 if ( oldData )
688 {
689 // Preserve the old values in case the user changed them.
690 m_imageData->SetBitmapPosition(oldData->GetBitmapPosition());
691
692 const wxSize oldMargins = oldData->GetBitmapMargins();
693 m_imageData->SetBitmapMargins(oldMargins.x, oldMargins.y);
694
695 // No need to preserve the bitmaps though as they were of wrong
696 // size anyhow.
697
698 delete oldData;
699 }
700 }
701 else
702 #endif // wxUSE_UXTHEME
703 {
704 m_imageData = new wxODButtonImageData(this, bitmap);
705 MakeOwnerDrawn();
706 }
707 }
708 else
709 {
710 m_imageData->SetBitmap(bitmap, which);
711 }
712
713 // it should be enough to only invalidate the best size when the normal
714 // bitmap changes as all bitmaps assigned to the button should be of the
715 // same size anyhow
716 if ( which == State_Normal )
717 InvalidateBestSize();
718
719 Refresh();
720 }
721
722 wxSize wxAnyButton::DoGetBitmapMargins() const
723 {
724 return m_imageData ? m_imageData->GetBitmapMargins() : wxSize(0, 0);
725 }
726
727 void wxAnyButton::DoSetBitmapMargins(wxCoord x, wxCoord y)
728 {
729 wxCHECK_RET( m_imageData, "SetBitmap() must be called first" );
730
731 m_imageData->SetBitmapMargins(x, y);
732 InvalidateBestSize();
733 }
734
735 void wxAnyButton::DoSetBitmapPosition(wxDirection dir)
736 {
737 wxCHECK_RET( m_imageData, "SetBitmap() must be called first" );
738
739 m_imageData->SetBitmapPosition(dir);
740 InvalidateBestSize();
741 }
742
743 // ----------------------------------------------------------------------------
744 // markup support
745 // ----------------------------------------------------------------------------
746
747 #if wxUSE_MARKUP
748
749 bool wxAnyButton::DoSetLabelMarkup(const wxString& markup)
750 {
751 if ( !wxAnyButtonBase::DoSetLabelMarkup(markup) )
752 return false;
753
754 if ( !m_markupText )
755 {
756 m_markupText = new wxMarkupText(markup);
757 MakeOwnerDrawn();
758 }
759 else
760 {
761 // We are already owner-drawn so just update the text.
762 m_markupText->SetMarkup(markup);
763 }
764
765 Refresh();
766
767 return true;
768 }
769
770 #endif // wxUSE_MARKUP
771
772 // ----------------------------------------------------------------------------
773 // owner-drawn buttons support
774 // ----------------------------------------------------------------------------
775
776 // drawing helpers
777 namespace
778 {
779
780 // return the button state using both the ODS_XXX flags specified in state
781 // parameter and the current button state
782 wxAnyButton::State GetButtonState(wxAnyButton *btn, UINT state)
783 {
784 if ( state & ODS_DISABLED )
785 return wxAnyButton::State_Disabled;
786
787 if ( state & ODS_SELECTED )
788 return wxAnyButton::State_Pressed;
789
790 if ( btn->HasCapture() || btn->IsMouseInWindow() )
791 return wxAnyButton::State_Current;
792
793 if ( state & ODS_FOCUS )
794 return wxAnyButton::State_Focused;
795
796 return btn->GetNormalState();
797 }
798
799 void DrawButtonText(HDC hdc,
800 RECT *pRect,
801 wxAnyButton *btn,
802 int flags)
803 {
804 const wxString text = btn->GetLabel();
805
806 if ( text.find(wxT('\n')) != wxString::npos )
807 {
808 // draw multiline label
809
810 // center text horizontally in any case
811 flags |= DT_CENTER;
812
813 // first we need to compute its bounding rect
814 RECT rc;
815 ::CopyRect(&rc, pRect);
816 ::DrawText(hdc, text.wx_str(), text.length(), &rc,
817 DT_CENTER | DT_CALCRECT);
818
819 // now center this rect inside the entire button area
820 const LONG w = rc.right - rc.left;
821 const LONG h = rc.bottom - rc.top;
822 rc.left = (pRect->right - pRect->left)/2 - w/2;
823 rc.right = rc.left+w;
824 rc.top = (pRect->bottom - pRect->top)/2 - h/2;
825 rc.bottom = rc.top+h;
826
827 ::DrawText(hdc, text.wx_str(), text.length(), &rc, flags);
828 }
829 else // single line label
830 {
831 // translate wx button flags to alignment flags for DrawText()
832 if ( btn->HasFlag(wxBU_RIGHT) )
833 {
834 flags |= DT_RIGHT;
835 }
836 else if ( !btn->HasFlag(wxBU_LEFT) )
837 {
838 flags |= DT_CENTER;
839 }
840 //else: DT_LEFT is the default anyhow (and its value is 0 too)
841
842 if ( btn->HasFlag(wxBU_BOTTOM) )
843 {
844 flags |= DT_BOTTOM;
845 }
846 else if ( !btn->HasFlag(wxBU_TOP) )
847 {
848 flags |= DT_VCENTER;
849 }
850 //else: as above, DT_TOP is the default
851
852 // notice that we must have DT_SINGLELINE for vertical alignment flags
853 // to work
854 ::DrawText(hdc, text.wx_str(), text.length(), pRect,
855 flags | DT_SINGLELINE );
856 }
857 }
858
859 void DrawRect(HDC hdc, const RECT& r)
860 {
861 wxDrawLine(hdc, r.left, r.top, r.right, r.top);
862 wxDrawLine(hdc, r.right, r.top, r.right, r.bottom);
863 wxDrawLine(hdc, r.right, r.bottom, r.left, r.bottom);
864 wxDrawLine(hdc, r.left, r.bottom, r.left, r.top);
865 }
866
867 /*
868 The button frame looks like this normally:
869
870 WWWWWWWWWWWWWWWWWWB
871 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
872 WH GB H = light grey (LIGHT)
873 WH GB G = dark grey (SHADOW)
874 WH GB B = black (DKSHADOW)
875 WH GB
876 WGGGGGGGGGGGGGGGGGB
877 BBBBBBBBBBBBBBBBBBB
878
879 When the button is selected, the button becomes like this (the total button
880 size doesn't change):
881
882 BBBBBBBBBBBBBBBBBBB
883 BWWWWWWWWWWWWWWWWBB
884 BWHHHHHHHHHHHHHHGBB
885 BWH GBB
886 BWH GBB
887 BWGGGGGGGGGGGGGGGBB
888 BBBBBBBBBBBBBBBBBBB
889 BBBBBBBBBBBBBBBBBBB
890
891 When the button is pushed (while selected) it is like:
892
893 BBBBBBBBBBBBBBBBBBB
894 BGGGGGGGGGGGGGGGGGB
895 BG GB
896 BG GB
897 BG GB
898 BG GB
899 BGGGGGGGGGGGGGGGGGB
900 BBBBBBBBBBBBBBBBBBB
901 */
902 void DrawButtonFrame(HDC hdc, RECT& rectBtn,
903 bool selected, bool pushed)
904 {
905 RECT r;
906 CopyRect(&r, &rectBtn);
907
908 AutoHPEN hpenBlack(GetSysColor(COLOR_3DDKSHADOW)),
909 hpenGrey(GetSysColor(COLOR_3DSHADOW)),
910 hpenLightGr(GetSysColor(COLOR_3DLIGHT)),
911 hpenWhite(GetSysColor(COLOR_3DHILIGHT));
912
913 SelectInHDC selectPen(hdc, hpenBlack);
914
915 r.right--;
916 r.bottom--;
917
918 if ( pushed )
919 {
920 DrawRect(hdc, r);
921
922 (void)SelectObject(hdc, hpenGrey);
923 ::InflateRect(&r, -1, -1);
924
925 DrawRect(hdc, r);
926 }
927 else // !pushed
928 {
929 if ( selected )
930 {
931 DrawRect(hdc, r);
932
933 ::InflateRect(&r, -1, -1);
934 }
935
936 wxDrawLine(hdc, r.left, r.bottom, r.right, r.bottom);
937 wxDrawLine(hdc, r.right, r.bottom, r.right, r.top - 1);
938
939 (void)SelectObject(hdc, hpenWhite);
940 wxDrawLine(hdc, r.left, r.bottom - 1, r.left, r.top);
941 wxDrawLine(hdc, r.left, r.top, r.right, r.top);
942
943 (void)SelectObject(hdc, hpenLightGr);
944 wxDrawLine(hdc, r.left + 1, r.bottom - 2, r.left + 1, r.top + 1);
945 wxDrawLine(hdc, r.left + 1, r.top + 1, r.right - 1, r.top + 1);
946
947 (void)SelectObject(hdc, hpenGrey);
948 wxDrawLine(hdc, r.left + 1, r.bottom - 1, r.right - 1, r.bottom - 1);
949 wxDrawLine(hdc, r.right - 1, r.bottom - 1, r.right - 1, r.top);
950 }
951
952 InflateRect(&rectBtn, -OD_BUTTON_MARGIN, -OD_BUTTON_MARGIN);
953 }
954
955 #if wxUSE_UXTHEME
956 void DrawXPBackground(wxAnyButton *button, HDC hdc, RECT& rectBtn, UINT state)
957 {
958 wxUxThemeHandle theme(button, L"BUTTON");
959
960 // this array is indexed by wxAnyButton::State values and so must be kept in
961 // sync with it
962 static const int uxStates[] =
963 {
964 PBS_NORMAL, PBS_HOT, PBS_PRESSED, PBS_DISABLED, PBS_DEFAULTED
965 };
966
967 int iState = uxStates[GetButtonState(button, state)];
968
969 wxUxThemeEngine * const engine = wxUxThemeEngine::Get();
970
971 // draw parent background if needed
972 if ( engine->IsThemeBackgroundPartiallyTransparent
973 (
974 theme,
975 BP_PUSHBUTTON,
976 iState
977 ) )
978 {
979 // Set this button as the one whose background is being erased: this
980 // allows our WM_ERASEBKGND handler used by DrawThemeParentBackground()
981 // to correctly align the background brush with this window instead of
982 // the parent window to which WM_ERASEBKGND is sent. Notice that this
983 // doesn't work with custom user-defined EVT_ERASE_BACKGROUND handlers
984 // as they won't be aligned but unfortunately all the attempts to fix
985 // it by shifting DC origin before calling DrawThemeParentBackground()
986 // failed to work so we at least do this, even though this is far from
987 // being the perfect solution.
988 wxWindowBeingErased = button;
989
990 engine->DrawThemeParentBackground(GetHwndOf(button), hdc, &rectBtn);
991
992 wxWindowBeingErased = NULL;
993 }
994
995 // draw background
996 engine->DrawThemeBackground(theme, hdc, BP_PUSHBUTTON, iState,
997 &rectBtn, NULL);
998
999 // calculate content area margins
1000 MARGINS margins;
1001 engine->GetThemeMargins(theme, hdc, BP_PUSHBUTTON, iState,
1002 TMT_CONTENTMARGINS, &rectBtn, &margins);
1003 ::InflateRect(&rectBtn, -margins.cxLeftWidth, -margins.cyTopHeight);
1004 ::InflateRect(&rectBtn, -XP_BUTTON_EXTRA_MARGIN, -XP_BUTTON_EXTRA_MARGIN);
1005
1006 if ( button->UseBgCol() )
1007 {
1008 COLORREF colBg = wxColourToRGB(button->GetBackgroundColour());
1009 AutoHBRUSH hbrushBackground(colBg);
1010
1011 // don't overwrite the focus rect
1012 RECT rectClient;
1013 ::CopyRect(&rectClient, &rectBtn);
1014 ::InflateRect(&rectClient, -1, -1);
1015 FillRect(hdc, &rectClient, hbrushBackground);
1016 }
1017 }
1018 #endif // wxUSE_UXTHEME
1019
1020 } // anonymous namespace
1021
1022 // ----------------------------------------------------------------------------
1023 // owner drawn buttons support
1024 // ----------------------------------------------------------------------------
1025
1026 void wxAnyButton::MakeOwnerDrawn()
1027 {
1028 if ( !IsOwnerDrawn() )
1029 {
1030 // make it so
1031 // note that BS_OWNERDRAW is not independent from other style bits
1032 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
1033 style &= ~(BS_3STATE | BS_AUTO3STATE | BS_AUTOCHECKBOX | BS_AUTORADIOBUTTON | BS_CHECKBOX | BS_DEFPUSHBUTTON | BS_GROUPBOX | BS_PUSHBUTTON | BS_RADIOBUTTON | BS_PUSHLIKE);
1034 style |= BS_OWNERDRAW;
1035 SetWindowLong(GetHwnd(), GWL_STYLE, style);
1036 }
1037 }
1038
1039 bool wxAnyButton::IsOwnerDrawn() const
1040 {
1041 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
1042 return ( (style & BS_OWNERDRAW) == BS_OWNERDRAW );
1043 }
1044
1045 bool wxAnyButton::SetBackgroundColour(const wxColour &colour)
1046 {
1047 if ( !wxControl::SetBackgroundColour(colour) )
1048 {
1049 // nothing to do
1050 return false;
1051 }
1052
1053 MakeOwnerDrawn();
1054
1055 Refresh();
1056
1057 return true;
1058 }
1059
1060 bool wxAnyButton::SetForegroundColour(const wxColour &colour)
1061 {
1062 if ( !wxControl::SetForegroundColour(colour) )
1063 {
1064 // nothing to do
1065 return false;
1066 }
1067
1068 MakeOwnerDrawn();
1069
1070 Refresh();
1071
1072 return true;
1073 }
1074
1075 bool wxAnyButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
1076 {
1077 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
1078 HDC hdc = lpDIS->hDC;
1079
1080 UINT state = lpDIS->itemState;
1081 switch ( GetButtonState(this, state) )
1082 {
1083 case State_Disabled:
1084 state |= ODS_DISABLED;
1085 break;
1086 case State_Pressed:
1087 state |= ODS_SELECTED;
1088 break;
1089 case State_Focused:
1090 state |= ODS_FOCUS;
1091 break;
1092 default:
1093 break;
1094 }
1095
1096 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
1097
1098 RECT rectBtn;
1099 CopyRect(&rectBtn, &lpDIS->rcItem);
1100
1101 // draw the button background
1102 if ( !HasFlag(wxBORDER_NONE) )
1103 {
1104 #if wxUSE_UXTHEME
1105 if ( wxUxThemeEngine::GetIfActive() )
1106 {
1107 DrawXPBackground(this, hdc, rectBtn, state);
1108 }
1109 else
1110 #endif // wxUSE_UXTHEME
1111 {
1112 COLORREF colBg = wxColourToRGB(GetBackgroundColour());
1113
1114 // first, draw the background
1115 AutoHBRUSH hbrushBackground(colBg);
1116 FillRect(hdc, &rectBtn, hbrushBackground);
1117
1118 // draw the border for the current state
1119 bool selected = (state & ODS_SELECTED) != 0;
1120 if ( !selected )
1121 {
1122 wxTopLevelWindow *
1123 tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
1124 if ( tlw )
1125 {
1126 selected = tlw->GetDefaultItem() == this;
1127 }
1128 }
1129
1130 DrawButtonFrame(hdc, rectBtn, selected, pushed);
1131 }
1132
1133 // draw the focus rectangle if we need it
1134 if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) )
1135 {
1136 DrawFocusRect(hdc, &rectBtn);
1137
1138 #if wxUSE_UXTHEME
1139 if ( !wxUxThemeEngine::GetIfActive() )
1140 #endif // wxUSE_UXTHEME
1141 {
1142 if ( pushed )
1143 {
1144 // the label is shifted by 1 pixel to create "pushed" effect
1145 OffsetRect(&rectBtn, 1, 1);
1146 }
1147 }
1148 }
1149 }
1150
1151
1152 // draw the image, if any
1153 if ( m_imageData )
1154 {
1155 wxBitmap bmp = m_imageData->GetBitmap(GetButtonState(this, state));
1156 if ( !bmp.IsOk() )
1157 bmp = m_imageData->GetBitmap(State_Normal);
1158
1159 const wxSize sizeBmp = bmp.GetSize();
1160 const wxSize margin = m_imageData->GetBitmapMargins();
1161 const wxSize sizeBmpWithMargins(sizeBmp + 2*margin);
1162 wxRect rectButton(wxRectFromRECT(rectBtn));
1163
1164 // for simplicity, we start with centred rectangle and then move it to
1165 // the appropriate edge
1166 wxRect rectBitmap = wxRect(sizeBmp).CentreIn(rectButton);
1167
1168 // move bitmap only if we have a label, otherwise keep it centered
1169 if ( ShowsLabel() )
1170 {
1171 switch ( m_imageData->GetBitmapPosition() )
1172 {
1173 default:
1174 wxFAIL_MSG( "invalid direction" );
1175 // fall through
1176
1177 case wxLEFT:
1178 rectBitmap.x = rectButton.x + margin.x;
1179 rectButton.x += sizeBmpWithMargins.x;
1180 rectButton.width -= sizeBmpWithMargins.x;
1181 break;
1182
1183 case wxRIGHT:
1184 rectBitmap.x = rectButton.GetRight() - sizeBmp.x - margin.x;
1185 rectButton.width -= sizeBmpWithMargins.x;
1186 break;
1187
1188 case wxTOP:
1189 rectBitmap.y = rectButton.y + margin.y;
1190 rectButton.y += sizeBmpWithMargins.y;
1191 rectButton.height -= sizeBmpWithMargins.y;
1192 break;
1193
1194 case wxBOTTOM:
1195 rectBitmap.y = rectButton.GetBottom() - sizeBmp.y - margin.y;
1196 rectButton.height -= sizeBmpWithMargins.y;
1197 break;
1198 }
1199 }
1200
1201 wxDCTemp dst((WXHDC)hdc);
1202 dst.DrawBitmap(bmp, rectBitmap.GetPosition(), true);
1203
1204 wxCopyRectToRECT(rectButton, rectBtn);
1205 }
1206
1207
1208 // finally draw the label
1209 if ( ShowsLabel() )
1210 {
1211 COLORREF colFg = state & ODS_DISABLED
1212 ? ::GetSysColor(COLOR_GRAYTEXT)
1213 : wxColourToRGB(GetForegroundColour());
1214
1215 wxTextColoursChanger changeFg(hdc, colFg, CLR_INVALID);
1216 wxBkModeChanger changeBkMode(hdc, wxBRUSHSTYLE_TRANSPARENT);
1217
1218 #if wxUSE_MARKUP
1219 if ( m_markupText )
1220 {
1221 wxDCTemp dc((WXHDC)hdc);
1222 dc.SetTextForeground(wxColour(colFg));
1223 dc.SetFont(GetFont());
1224
1225 m_markupText->Render(dc, wxRectFromRECT(rectBtn),
1226 state & ODS_NOACCEL
1227 ? wxMarkupText::Render_Default
1228 : wxMarkupText::Render_ShowAccels);
1229 }
1230 else // Plain text label
1231 #endif // wxUSE_MARKUP
1232 {
1233 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000)
1234 // systems but by happy coincidence ODS_NOACCEL is not used under
1235 // them neither so DT_HIDEPREFIX should never be used there
1236 DrawButtonText(hdc, &rectBtn, this,
1237 state & ODS_NOACCEL ? DT_HIDEPREFIX : 0);
1238 }
1239 }
1240
1241 return true;
1242 }
1243
1244 #endif // wxHAS_ANY_BUTTON