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