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