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