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