Fix appearance of multiline wxCheckBox with non-standard colours in wxMSW.
[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 // All buttons have at least the standard height and, unless the user
433 // explicitly wants them to be as small as possible and used wxBU_EXACTFIT
434 // style to indicate this, of at least the standard width too.
435 //
436 // Notice that we really want to make all buttons equally high, otherwise
437 // they look ugly and the existing code using wxBU_EXACTFIT only uses it to
438 // control width and not height.
439
440 // The 50x14 button size is documented in the "Recommended sizing and
441 // spacing" section of MSDN layout article.
442 //
443 // Note that we intentionally don't use GetDefaultSize() here, because
444 // it's inexact -- dialog units depend on this dialog's font.
445 const wxSize sizeDef = btn->ConvertDialogToPixels(wxSize(50, 14));
446 if ( !btn->HasFlag(wxBU_EXACTFIT) )
447 {
448 if ( sizeBtn.x < sizeDef.x )
449 sizeBtn.x = sizeDef.x;
450 }
451 if ( sizeBtn.y < sizeDef.y )
452 sizeBtn.y = sizeDef.y;
453
454 btn->CacheBestSize(sizeBtn);
455
456 return sizeBtn;
457 }
458
459 // ----------------------------------------------------------------------------
460 // creation/destruction
461 // ----------------------------------------------------------------------------
462
463 wxAnyButton::~wxAnyButton()
464 {
465 delete m_imageData;
466 #if wxUSE_MARKUP
467 delete m_markupText;
468 #endif // wxUSE_MARKUP
469 }
470
471 void wxAnyButton::SetLabel(const wxString& label)
472 {
473 wxMSWButton::UpdateMultilineStyle(GetHwnd(), label);
474
475 wxAnyButtonBase::SetLabel(label);
476
477 #if wxUSE_MARKUP
478 // If we have a plain text label, we shouldn't be using markup any longer.
479 if ( m_markupText )
480 {
481 delete m_markupText;
482 m_markupText = NULL;
483
484 // Unfortunately we don't really know whether we can reset the button
485 // to be non-owner-drawn or not: if we had made it owner-drawn just
486 // because of a call to SetLabelMarkup(), we could, but not if there
487 // were [also] calls to Set{Fore,Back}groundColour(). If it's really a
488 // problem to have button remain owner-drawn forever just because it
489 // had markup label once, we should record the reason for our current
490 // owner-drawnness and check it here.
491 }
492 #endif // wxUSE_MARKUP
493 }
494
495 // ----------------------------------------------------------------------------
496 // size management including autosizing
497 // ----------------------------------------------------------------------------
498
499 void wxAnyButton::AdjustForBitmapSize(wxSize &size) const
500 {
501 wxCHECK_RET( m_imageData, wxT("shouldn't be called if no image") );
502
503 // account for the bitmap size
504 const wxSize sizeBmp = m_imageData->GetBitmap(State_Normal).GetSize();
505 const wxDirection dirBmp = m_imageData->GetBitmapPosition();
506 if ( dirBmp == wxLEFT || dirBmp == wxRIGHT )
507 {
508 size.x += sizeBmp.x;
509 if ( sizeBmp.y > size.y )
510 size.y = sizeBmp.y;
511 }
512 else // bitmap on top/below the text
513 {
514 size.y += sizeBmp.y;
515 if ( sizeBmp.x > size.x )
516 size.x = sizeBmp.x;
517 }
518
519 // account for the user-specified margins
520 size += 2*m_imageData->GetBitmapMargins();
521
522 // and also for the margins we always add internally (unless we have no
523 // border at all in which case the button has exactly the same size as
524 // bitmap and so no margins should be used)
525 if ( !HasFlag(wxBORDER_NONE) )
526 {
527 int marginH = 0,
528 marginV = 0;
529 #if wxUSE_UXTHEME
530 if ( wxUxThemeEngine::GetIfActive() )
531 {
532 wxUxThemeHandle theme(const_cast<wxAnyButton *>(this), L"BUTTON");
533
534 MARGINS margins;
535 wxUxThemeEngine::Get()->GetThemeMargins(theme, NULL,
536 BP_PUSHBUTTON,
537 PBS_NORMAL,
538 TMT_CONTENTMARGINS,
539 NULL,
540 &margins);
541
542 // XP doesn't draw themed buttons correctly when the client
543 // area is smaller than 8x8 - enforce this minimum size for
544 // small bitmaps
545 size.IncTo(wxSize(8, 8));
546
547 marginH = margins.cxLeftWidth + margins.cxRightWidth
548 + 2*XP_BUTTON_EXTRA_MARGIN;
549 marginV = margins.cyTopHeight + margins.cyBottomHeight
550 + 2*XP_BUTTON_EXTRA_MARGIN;
551 }
552 else
553 #endif // wxUSE_UXTHEME
554 {
555 marginH =
556 marginV = OD_BUTTON_MARGIN;
557 }
558
559 size.IncBy(marginH, marginV);
560 }
561 }
562
563 wxSize wxAnyButton::DoGetBestSize() const
564 {
565 wxAnyButton * const self = const_cast<wxAnyButton *>(this);
566
567 wxSize size;
568
569 // Account for the text part if we have it.
570 if ( ShowsLabel() )
571 {
572 int flags = 0;
573 if ( HasFlag(wxBU_EXACTFIT) )
574 flags |= wxMSWButton::Size_ExactFit;
575 if ( DoGetAuthNeeded() )
576 flags |= wxMSWButton::Size_AuthNeeded;
577
578 #if wxUSE_MARKUP
579 if ( m_markupText )
580 {
581 wxClientDC dc(self);
582 size = wxMSWButton::GetFittingSize(self,
583 m_markupText->Measure(dc),
584 flags);
585 }
586 else // Normal plain text (but possibly multiline) label.
587 #endif // wxUSE_MARKUP
588 {
589 size = wxMSWButton::ComputeBestFittingSize(self, flags);
590 }
591 }
592
593 if ( m_imageData )
594 AdjustForBitmapSize(size);
595
596 return wxMSWButton::IncreaseToStdSizeAndCache(self, size);
597 }
598
599 // ----------------------------------------------------------------------------
600 // event/message handlers
601 // ----------------------------------------------------------------------------
602
603 WXLRESULT wxAnyButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
604 {
605 if ( nMsg == WM_LBUTTONDBLCLK )
606 {
607 // emulate a click event to force an owner-drawn button to change its
608 // appearance - without this, it won't do it
609 (void)wxControl::MSWWindowProc(WM_LBUTTONDOWN, wParam, lParam);
610
611 // and continue with processing the message normally as well
612 }
613 #if wxUSE_UXTHEME
614 else if ( nMsg == WM_THEMECHANGED )
615 {
616 // need to recalculate the best size here
617 // as the theme size might have changed
618 InvalidateBestSize();
619 }
620 #endif // wxUSE_UXTHEME
621 // must use m_mouseInWindow here instead of IsMouseInWindow()
622 // since we need to know the first time the mouse enters the window
623 // and IsMouseInWindow() would return true in this case
624 else if ( (nMsg == WM_MOUSEMOVE && !m_mouseInWindow) ||
625 nMsg == WM_MOUSELEAVE )
626 {
627 if (
628 IsEnabled() &&
629 (
630 #if wxUSE_UXTHEME
631 wxUxThemeEngine::GetIfActive() ||
632 #endif // wxUSE_UXTHEME
633 (m_imageData && m_imageData->GetBitmap(State_Current).IsOk())
634 )
635 )
636 {
637 Refresh();
638 }
639 }
640
641 // let the base class do all real processing
642 return wxControl::MSWWindowProc(nMsg, wParam, lParam);
643 }
644
645 // ----------------------------------------------------------------------------
646 // button images
647 // ----------------------------------------------------------------------------
648
649 wxBitmap wxAnyButton::DoGetBitmap(State which) const
650 {
651 return m_imageData ? m_imageData->GetBitmap(which) : wxBitmap();
652 }
653
654 void wxAnyButton::DoSetBitmap(const wxBitmap& bitmap, State which)
655 {
656 #if wxUSE_UXTHEME
657 wxXPButtonImageData *oldData = NULL;
658 #endif // wxUSE_UXTHEME
659
660 // Check if we already had bitmaps of different size.
661 if ( m_imageData &&
662 bitmap.GetSize() != m_imageData->GetBitmap(State_Normal).GetSize() )
663 {
664 wxASSERT_MSG( (which == State_Normal) || bitmap.IsNull(),
665 "Must set normal bitmap with the new size first" );
666
667 #if wxUSE_UXTHEME
668 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
669 {
670 // We can't change the size of the images stored in wxImageList
671 // in wxXPButtonImageData::m_iml so force recreating it below but
672 // keep the current data to copy its values into the new one.
673 oldData = static_cast<wxXPButtonImageData *>(m_imageData);
674 m_imageData = NULL;
675 }
676 #endif // wxUSE_UXTHEME
677 //else: wxODButtonImageData doesn't require anything special
678 }
679
680 // allocate the image data when the first bitmap is set
681 if ( !m_imageData )
682 {
683 #if wxUSE_UXTHEME
684 // using image list doesn't work correctly if we don't have any label
685 // (even if we use BUTTON_IMAGELIST_ALIGN_CENTER alignment and
686 // BS_BITMAP style), at least under Windows 2003 so use owner drawn
687 // strategy for bitmap-only buttons
688 if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() )
689 {
690 m_imageData = new wxXPButtonImageData(this, bitmap);
691
692 if ( oldData )
693 {
694 // Preserve the old values in case the user changed them.
695 m_imageData->SetBitmapPosition(oldData->GetBitmapPosition());
696
697 const wxSize oldMargins = oldData->GetBitmapMargins();
698 m_imageData->SetBitmapMargins(oldMargins.x, oldMargins.y);
699
700 // No need to preserve the bitmaps though as they were of wrong
701 // size anyhow.
702
703 delete oldData;
704 }
705 }
706 else
707 #endif // wxUSE_UXTHEME
708 {
709 m_imageData = new wxODButtonImageData(this, bitmap);
710 MakeOwnerDrawn();
711 }
712 }
713 else
714 {
715 m_imageData->SetBitmap(bitmap, which);
716 }
717
718 // it should be enough to only invalidate the best size when the normal
719 // bitmap changes as all bitmaps assigned to the button should be of the
720 // same size anyhow
721 if ( which == State_Normal )
722 InvalidateBestSize();
723
724 Refresh();
725 }
726
727 wxSize wxAnyButton::DoGetBitmapMargins() const
728 {
729 return m_imageData ? m_imageData->GetBitmapMargins() : wxSize(0, 0);
730 }
731
732 void wxAnyButton::DoSetBitmapMargins(wxCoord x, wxCoord y)
733 {
734 wxCHECK_RET( m_imageData, "SetBitmap() must be called first" );
735
736 m_imageData->SetBitmapMargins(x, y);
737 InvalidateBestSize();
738 }
739
740 void wxAnyButton::DoSetBitmapPosition(wxDirection dir)
741 {
742 wxCHECK_RET( m_imageData, "SetBitmap() must be called first" );
743
744 m_imageData->SetBitmapPosition(dir);
745 InvalidateBestSize();
746 }
747
748 // ----------------------------------------------------------------------------
749 // markup support
750 // ----------------------------------------------------------------------------
751
752 #if wxUSE_MARKUP
753
754 bool wxAnyButton::DoSetLabelMarkup(const wxString& markup)
755 {
756 if ( !wxAnyButtonBase::DoSetLabelMarkup(markup) )
757 return false;
758
759 if ( !m_markupText )
760 {
761 m_markupText = new wxMarkupText(markup);
762 MakeOwnerDrawn();
763 }
764 else
765 {
766 // We are already owner-drawn so just update the text.
767 m_markupText->SetMarkup(markup);
768 }
769
770 Refresh();
771
772 return true;
773 }
774
775 #endif // wxUSE_MARKUP
776
777 // ----------------------------------------------------------------------------
778 // owner-drawn buttons support
779 // ----------------------------------------------------------------------------
780
781 // drawing helpers
782 namespace
783 {
784
785 // return the button state using both the ODS_XXX flags specified in state
786 // parameter and the current button state
787 wxAnyButton::State GetButtonState(wxAnyButton *btn, UINT state)
788 {
789 if ( state & ODS_DISABLED )
790 return wxAnyButton::State_Disabled;
791
792 if ( state & ODS_SELECTED )
793 return wxAnyButton::State_Pressed;
794
795 if ( btn->HasCapture() || btn->IsMouseInWindow() )
796 return wxAnyButton::State_Current;
797
798 if ( state & ODS_FOCUS )
799 return wxAnyButton::State_Focused;
800
801 return btn->GetNormalState();
802 }
803
804 void DrawButtonText(HDC hdc,
805 RECT *pRect,
806 wxAnyButton *btn,
807 int flags)
808 {
809 const wxString text = btn->GetLabel();
810
811 if ( text.find(wxT('\n')) != wxString::npos )
812 {
813 // draw multiline label
814
815 // center text horizontally in any case
816 flags |= DT_CENTER;
817
818 // first we need to compute its bounding rect
819 RECT rc;
820 ::CopyRect(&rc, pRect);
821 ::DrawText(hdc, text.t_str(), text.length(), &rc,
822 DT_CENTER | DT_CALCRECT);
823
824 // now center this rect inside the entire button area
825 const LONG w = rc.right - rc.left;
826 const LONG h = rc.bottom - rc.top;
827 rc.left = pRect->left + (pRect->right - pRect->left)/2 - w/2;
828 rc.right = rc.left+w;
829 rc.top = pRect->top + (pRect->bottom - pRect->top)/2 - h/2;
830 rc.bottom = rc.top+h;
831
832 ::DrawText(hdc, text.t_str(), text.length(), &rc, flags);
833 }
834 else // single line label
835 {
836 // translate wx button flags to alignment flags for DrawText()
837 if ( btn->HasFlag(wxBU_RIGHT) )
838 {
839 flags |= DT_RIGHT;
840 }
841 else if ( !btn->HasFlag(wxBU_LEFT) )
842 {
843 flags |= DT_CENTER;
844 }
845 //else: DT_LEFT is the default anyhow (and its value is 0 too)
846
847 if ( btn->HasFlag(wxBU_BOTTOM) )
848 {
849 flags |= DT_BOTTOM;
850 }
851 else if ( !btn->HasFlag(wxBU_TOP) )
852 {
853 flags |= DT_VCENTER;
854 }
855 //else: as above, DT_TOP is the default
856
857 // notice that we must have DT_SINGLELINE for vertical alignment flags
858 // to work
859 ::DrawText(hdc, text.t_str(), text.length(), pRect,
860 flags | DT_SINGLELINE );
861 }
862 }
863
864 void DrawRect(HDC hdc, const RECT& r)
865 {
866 wxDrawLine(hdc, r.left, r.top, r.right, r.top);
867 wxDrawLine(hdc, r.right, r.top, r.right, r.bottom);
868 wxDrawLine(hdc, r.right, r.bottom, r.left, r.bottom);
869 wxDrawLine(hdc, r.left, r.bottom, r.left, r.top);
870 }
871
872 /*
873 The button frame looks like this normally:
874
875 WWWWWWWWWWWWWWWWWWB
876 WHHHHHHHHHHHHHHHHGB W = white (HILIGHT)
877 WH GB H = light grey (LIGHT)
878 WH GB G = dark grey (SHADOW)
879 WH GB B = black (DKSHADOW)
880 WH GB
881 WGGGGGGGGGGGGGGGGGB
882 BBBBBBBBBBBBBBBBBBB
883
884 When the button is selected, the button becomes like this (the total button
885 size doesn't change):
886
887 BBBBBBBBBBBBBBBBBBB
888 BWWWWWWWWWWWWWWWWBB
889 BWHHHHHHHHHHHHHHGBB
890 BWH GBB
891 BWH GBB
892 BWGGGGGGGGGGGGGGGBB
893 BBBBBBBBBBBBBBBBBBB
894 BBBBBBBBBBBBBBBBBBB
895
896 When the button is pushed (while selected) it is like:
897
898 BBBBBBBBBBBBBBBBBBB
899 BGGGGGGGGGGGGGGGGGB
900 BG GB
901 BG GB
902 BG GB
903 BG GB
904 BGGGGGGGGGGGGGGGGGB
905 BBBBBBBBBBBBBBBBBBB
906 */
907 void DrawButtonFrame(HDC hdc, RECT& rectBtn,
908 bool selected, bool pushed)
909 {
910 RECT r;
911 CopyRect(&r, &rectBtn);
912
913 AutoHPEN hpenBlack(GetSysColor(COLOR_3DDKSHADOW)),
914 hpenGrey(GetSysColor(COLOR_3DSHADOW)),
915 hpenLightGr(GetSysColor(COLOR_3DLIGHT)),
916 hpenWhite(GetSysColor(COLOR_3DHILIGHT));
917
918 SelectInHDC selectPen(hdc, hpenBlack);
919
920 r.right--;
921 r.bottom--;
922
923 if ( pushed )
924 {
925 DrawRect(hdc, r);
926
927 (void)SelectObject(hdc, hpenGrey);
928 ::InflateRect(&r, -1, -1);
929
930 DrawRect(hdc, r);
931 }
932 else // !pushed
933 {
934 if ( selected )
935 {
936 DrawRect(hdc, r);
937
938 ::InflateRect(&r, -1, -1);
939 }
940
941 wxDrawLine(hdc, r.left, r.bottom, r.right, r.bottom);
942 wxDrawLine(hdc, r.right, r.bottom, r.right, r.top - 1);
943
944 (void)SelectObject(hdc, hpenWhite);
945 wxDrawLine(hdc, r.left, r.bottom - 1, r.left, r.top);
946 wxDrawLine(hdc, r.left, r.top, r.right, r.top);
947
948 (void)SelectObject(hdc, hpenLightGr);
949 wxDrawLine(hdc, r.left + 1, r.bottom - 2, r.left + 1, r.top + 1);
950 wxDrawLine(hdc, r.left + 1, r.top + 1, r.right - 1, r.top + 1);
951
952 (void)SelectObject(hdc, hpenGrey);
953 wxDrawLine(hdc, r.left + 1, r.bottom - 1, r.right - 1, r.bottom - 1);
954 wxDrawLine(hdc, r.right - 1, r.bottom - 1, r.right - 1, r.top);
955 }
956
957 InflateRect(&rectBtn, -OD_BUTTON_MARGIN, -OD_BUTTON_MARGIN);
958 }
959
960 #if wxUSE_UXTHEME
961 void DrawXPBackground(wxAnyButton *button, HDC hdc, RECT& rectBtn, UINT state)
962 {
963 wxUxThemeHandle theme(button, L"BUTTON");
964
965 // this array is indexed by wxAnyButton::State values and so must be kept in
966 // sync with it
967 static const int uxStates[] =
968 {
969 PBS_NORMAL, PBS_HOT, PBS_PRESSED, PBS_DISABLED, PBS_DEFAULTED
970 };
971
972 int iState = uxStates[GetButtonState(button, state)];
973
974 wxUxThemeEngine * const engine = wxUxThemeEngine::Get();
975
976 // draw parent background if needed
977 if ( engine->IsThemeBackgroundPartiallyTransparent
978 (
979 theme,
980 BP_PUSHBUTTON,
981 iState
982 ) )
983 {
984 // Set this button as the one whose background is being erased: this
985 // allows our WM_ERASEBKGND handler used by DrawThemeParentBackground()
986 // to correctly align the background brush with this window instead of
987 // the parent window to which WM_ERASEBKGND is sent. Notice that this
988 // doesn't work with custom user-defined EVT_ERASE_BACKGROUND handlers
989 // as they won't be aligned but unfortunately all the attempts to fix
990 // it by shifting DC origin before calling DrawThemeParentBackground()
991 // failed to work so we at least do this, even though this is far from
992 // being the perfect solution.
993 wxWindowBeingErased = button;
994
995 engine->DrawThemeParentBackground(GetHwndOf(button), hdc, &rectBtn);
996
997 wxWindowBeingErased = NULL;
998 }
999
1000 // draw background
1001 engine->DrawThemeBackground(theme, hdc, BP_PUSHBUTTON, iState,
1002 &rectBtn, NULL);
1003
1004 // calculate content area margins
1005 MARGINS margins;
1006 engine->GetThemeMargins(theme, hdc, BP_PUSHBUTTON, iState,
1007 TMT_CONTENTMARGINS, &rectBtn, &margins);
1008 ::InflateRect(&rectBtn, -margins.cxLeftWidth, -margins.cyTopHeight);
1009 ::InflateRect(&rectBtn, -XP_BUTTON_EXTRA_MARGIN, -XP_BUTTON_EXTRA_MARGIN);
1010
1011 if ( button->UseBgCol() )
1012 {
1013 COLORREF colBg = wxColourToRGB(button->GetBackgroundColour());
1014 AutoHBRUSH hbrushBackground(colBg);
1015
1016 // don't overwrite the focus rect
1017 RECT rectClient;
1018 ::CopyRect(&rectClient, &rectBtn);
1019 ::InflateRect(&rectClient, -1, -1);
1020 FillRect(hdc, &rectClient, hbrushBackground);
1021 }
1022 }
1023 #endif // wxUSE_UXTHEME
1024
1025 } // anonymous namespace
1026
1027 // ----------------------------------------------------------------------------
1028 // owner drawn buttons support
1029 // ----------------------------------------------------------------------------
1030
1031 void wxAnyButton::MakeOwnerDrawn()
1032 {
1033 if ( !IsOwnerDrawn() )
1034 {
1035 // make it so
1036 // note that BS_OWNERDRAW is not independent from other style bits
1037 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
1038 style &= ~(BS_3STATE | BS_AUTO3STATE | BS_AUTOCHECKBOX | BS_AUTORADIOBUTTON | BS_CHECKBOX | BS_DEFPUSHBUTTON | BS_GROUPBOX | BS_PUSHBUTTON | BS_RADIOBUTTON | BS_PUSHLIKE);
1039 style |= BS_OWNERDRAW;
1040 SetWindowLong(GetHwnd(), GWL_STYLE, style);
1041 }
1042 }
1043
1044 bool wxAnyButton::IsOwnerDrawn() const
1045 {
1046 long style = GetWindowLong(GetHwnd(), GWL_STYLE);
1047 return ( (style & BS_OWNERDRAW) == BS_OWNERDRAW );
1048 }
1049
1050 bool wxAnyButton::SetBackgroundColour(const wxColour &colour)
1051 {
1052 if ( !wxControl::SetBackgroundColour(colour) )
1053 {
1054 // nothing to do
1055 return false;
1056 }
1057
1058 MakeOwnerDrawn();
1059
1060 Refresh();
1061
1062 return true;
1063 }
1064
1065 bool wxAnyButton::SetForegroundColour(const wxColour &colour)
1066 {
1067 if ( !wxControl::SetForegroundColour(colour) )
1068 {
1069 // nothing to do
1070 return false;
1071 }
1072
1073 MakeOwnerDrawn();
1074
1075 Refresh();
1076
1077 return true;
1078 }
1079
1080 bool wxAnyButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis)
1081 {
1082 LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis;
1083 HDC hdc = lpDIS->hDC;
1084
1085 UINT state = lpDIS->itemState;
1086 switch ( GetButtonState(this, state) )
1087 {
1088 case State_Disabled:
1089 state |= ODS_DISABLED;
1090 break;
1091 case State_Pressed:
1092 state |= ODS_SELECTED;
1093 break;
1094 case State_Focused:
1095 state |= ODS_FOCUS;
1096 break;
1097 default:
1098 break;
1099 }
1100
1101 bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0;
1102
1103 RECT rectBtn;
1104 CopyRect(&rectBtn, &lpDIS->rcItem);
1105
1106 // draw the button background
1107 if ( !HasFlag(wxBORDER_NONE) )
1108 {
1109 #if wxUSE_UXTHEME
1110 if ( wxUxThemeEngine::GetIfActive() )
1111 {
1112 DrawXPBackground(this, hdc, rectBtn, state);
1113 }
1114 else
1115 #endif // wxUSE_UXTHEME
1116 {
1117 COLORREF colBg = wxColourToRGB(GetBackgroundColour());
1118
1119 // first, draw the background
1120 AutoHBRUSH hbrushBackground(colBg);
1121 FillRect(hdc, &rectBtn, hbrushBackground);
1122
1123 // draw the border for the current state
1124 bool selected = (state & ODS_SELECTED) != 0;
1125 if ( !selected )
1126 {
1127 wxTopLevelWindow *
1128 tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow);
1129 if ( tlw )
1130 {
1131 selected = tlw->GetDefaultItem() == this;
1132 }
1133 }
1134
1135 DrawButtonFrame(hdc, rectBtn, selected, pushed);
1136 }
1137
1138 // draw the focus rectangle if we need it
1139 if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) )
1140 {
1141 DrawFocusRect(hdc, &rectBtn);
1142
1143 #if wxUSE_UXTHEME
1144 if ( !wxUxThemeEngine::GetIfActive() )
1145 #endif // wxUSE_UXTHEME
1146 {
1147 if ( pushed )
1148 {
1149 // the label is shifted by 1 pixel to create "pushed" effect
1150 OffsetRect(&rectBtn, 1, 1);
1151 }
1152 }
1153 }
1154 }
1155
1156
1157 // draw the image, if any
1158 if ( m_imageData )
1159 {
1160 wxBitmap bmp = m_imageData->GetBitmap(GetButtonState(this, state));
1161 if ( !bmp.IsOk() )
1162 bmp = m_imageData->GetBitmap(State_Normal);
1163
1164 const wxSize sizeBmp = bmp.GetSize();
1165 const wxSize margin = m_imageData->GetBitmapMargins();
1166 const wxSize sizeBmpWithMargins(sizeBmp + 2*margin);
1167 wxRect rectButton(wxRectFromRECT(rectBtn));
1168
1169 // for simplicity, we start with centred rectangle and then move it to
1170 // the appropriate edge
1171 wxRect rectBitmap = wxRect(sizeBmp).CentreIn(rectButton);
1172
1173 // move bitmap only if we have a label, otherwise keep it centered
1174 if ( ShowsLabel() )
1175 {
1176 switch ( m_imageData->GetBitmapPosition() )
1177 {
1178 default:
1179 wxFAIL_MSG( "invalid direction" );
1180 // fall through
1181
1182 case wxLEFT:
1183 rectBitmap.x = rectButton.x + margin.x;
1184 rectButton.x += sizeBmpWithMargins.x;
1185 rectButton.width -= sizeBmpWithMargins.x;
1186 break;
1187
1188 case wxRIGHT:
1189 rectBitmap.x = rectButton.GetRight() - sizeBmp.x - margin.x;
1190 rectButton.width -= sizeBmpWithMargins.x;
1191 break;
1192
1193 case wxTOP:
1194 rectBitmap.y = rectButton.y + margin.y;
1195 rectButton.y += sizeBmpWithMargins.y;
1196 rectButton.height -= sizeBmpWithMargins.y;
1197 break;
1198
1199 case wxBOTTOM:
1200 rectBitmap.y = rectButton.GetBottom() - sizeBmp.y - margin.y;
1201 rectButton.height -= sizeBmpWithMargins.y;
1202 break;
1203 }
1204 }
1205
1206 wxDCTemp dst((WXHDC)hdc);
1207 dst.DrawBitmap(bmp, rectBitmap.GetPosition(), true);
1208
1209 wxCopyRectToRECT(rectButton, rectBtn);
1210 }
1211
1212
1213 // finally draw the label
1214 if ( ShowsLabel() )
1215 {
1216 COLORREF colFg = state & ODS_DISABLED
1217 ? ::GetSysColor(COLOR_GRAYTEXT)
1218 : wxColourToRGB(GetForegroundColour());
1219
1220 wxTextColoursChanger changeFg(hdc, colFg, CLR_INVALID);
1221 wxBkModeChanger changeBkMode(hdc, wxBRUSHSTYLE_TRANSPARENT);
1222
1223 #if wxUSE_MARKUP
1224 if ( m_markupText )
1225 {
1226 wxDCTemp dc((WXHDC)hdc);
1227 dc.SetTextForeground(wxColour(colFg));
1228 dc.SetFont(GetFont());
1229
1230 m_markupText->Render(dc, wxRectFromRECT(rectBtn),
1231 state & ODS_NOACCEL
1232 ? wxMarkupText::Render_Default
1233 : wxMarkupText::Render_ShowAccels);
1234 }
1235 else // Plain text label
1236 #endif // wxUSE_MARKUP
1237 {
1238 // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000)
1239 // systems but by happy coincidence ODS_NOACCEL is not used under
1240 // them neither so DT_HIDEPREFIX should never be used there
1241 DrawButtonText(hdc, &rectBtn, this,
1242 state & ODS_NOACCEL ? DT_HIDEPREFIX : 0);
1243 }
1244 }
1245
1246 return true;
1247 }
1248
1249 #endif // wxHAS_ANY_BUTTON