From 233f10bf15c7201e8733426cffdeb6595df539ae Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 15 Jun 2009 04:23:54 +0000 Subject: [PATCH] implement images support for wxButton under XP and later git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@61054 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/button.h | 18 ++- include/wx/msw/button.h | 11 +- samples/widgets/button.cpp | 40 +++-- src/common/btncmn.cpp | 12 ++ src/msw/button.cpp | 294 ++++++++++++++++++++++++++++++++++++- 5 files changed, 359 insertions(+), 16 deletions(-) diff --git a/include/wx/button.h b/include/wx/button.h index 9feabd6e1c..e2d3dbcd85 100644 --- a/include/wx/button.h +++ b/include/wx/button.h @@ -99,7 +99,7 @@ public: // set the image position relative to the text, i.e. wxLEFT means that the // image is to the left of the text (this is the default) - virtual void SetBitmapPosition(wxDirection WXUNUSED(dir)) { } + void SetBitmapPosition(wxDirection dir); // make this button the default button in its top level window @@ -130,20 +130,26 @@ public: void SetBitmapSelected(const wxBitmap& bitmap) { SetBitmapPressed(bitmap); } void SetBitmapHover(const wxBitmap& bitmap) { SetBitmapCurrent(bitmap); } -protected: - // choose the default border for this window - virtual wxBorder GetDefaultBorder() const { return wxBORDER_NONE; } + // this enum is not part of wx public API, it is public because it is used + // in non wxButton-derived classes internally + // + // also notice that MSW code relies on the values of the enum elements, do + // not change them without revising src/msw/button.cpp enum State { State_Normal, - State_Pressed, // a.k.a. "selected" in public API for some reason State_Current, // a.k.a. hot or "hovering" + State_Pressed, // a.k.a. "selected" in public API for some reason State_Disabled, State_Focused, State_Max }; +protected: + // choose the default border for this window + virtual wxBorder GetDefaultBorder() const { return wxBORDER_NONE; } + virtual wxBitmap DoGetBitmap(State WXUNUSED(which)) const { return wxBitmap(); } virtual void DoSetBitmap(const wxBitmap& WXUNUSED(bitmap), @@ -151,6 +157,8 @@ protected: { } virtual void DoSetBitmapMargins(wxCoord WXUNUSED(x), wxCoord WXUNUSED(y)) { } + virtual void DoSetBitmapPosition(wxDirection WXUNUSED(dir)) + { } wxDECLARE_NO_COPY_CLASS(wxButtonBase); diff --git a/include/wx/msw/button.h b/include/wx/msw/button.h index 1ee21c4d1e..19d204f2e1 100644 --- a/include/wx/msw/button.h +++ b/include/wx/msw/button.h @@ -19,7 +19,7 @@ class WXDLLIMPEXP_CORE wxButton : public wxButtonBase { public: - wxButton() { } + wxButton() { m_imageData = NULL; } wxButton(wxWindow *parent, wxWindowID id, const wxString& label = wxEmptyString, @@ -29,6 +29,8 @@ public: const wxValidator& validator = wxDefaultValidator, const wxString& name = wxButtonNameStr) { + m_imageData = NULL; + Create(parent, id, label, pos, size, style, validator, name); } @@ -78,6 +80,13 @@ protected: // usually overridden base class virtuals virtual wxSize DoGetBestSize() const; + virtual wxBitmap DoGetBitmap(State which) const; + virtual void DoSetBitmap(const wxBitmap& bitmap, State which); + virtual void DoSetBitmapMargins(wxCoord x, wxCoord y); + virtual void DoSetBitmapPosition(wxDirection dir); + + class wxButtonImageData *m_imageData; + private: DECLARE_DYNAMIC_CLASS_NO_COPY(wxButton) }; diff --git a/samples/widgets/button.cpp b/samples/widgets/button.cpp index 914fa1a08b..c01bc1cd27 100644 --- a/samples/widgets/button.cpp +++ b/samples/widgets/button.cpp @@ -58,6 +58,14 @@ enum }; // radio boxes +enum +{ + ButtonImagePos_Left, + ButtonImagePos_Right, + ButtonImagePos_Top, + ButtonImagePos_Bottom +}; + enum { ButtonHAlign_Left, @@ -124,6 +132,9 @@ protected: *m_chkUseHover, *m_chkUseDisabled; + // and an image position choice used if m_chkImage is on + wxRadioBox *m_radioImagePos; + wxRadioBox *m_radioHAlign, *m_radioVAlign; @@ -179,6 +190,7 @@ ButtonWidgetsPage::ButtonWidgetsPage(WidgetsBookCtrl *book, m_chkUseHover = m_chkUseDisabled = (wxCheckBox *)NULL; + m_radioImagePos = m_radioHAlign = m_radioVAlign = (wxRadioBox *)NULL; @@ -202,21 +214,26 @@ void ButtonWidgetsPage::CreateContent() m_chkFit = CreateCheckBoxAndAddToSizer(sizerLeft, _T("&Fit exactly")); m_chkDefault = CreateCheckBoxAndAddToSizer(sizerLeft, _T("&Default")); -#ifndef __WXUNIVERSAL__ - // only wxUniv currently supports buttons with images - m_chkImage->Disable(); -#endif // !wxUniv - sizerLeft->AddSpacer(5); wxSizer *sizerUseLabels = - new wxStaticBoxSizer(wxVERTICAL, this, _T("&Use the following labels?")); + new wxStaticBoxSizer(wxVERTICAL, this, _T("&Use the following bitmaps?")); m_chkUseSelected = CreateCheckBoxAndAddToSizer(sizerUseLabels, _T("&Pushed")); m_chkUseFocused = CreateCheckBoxAndAddToSizer(sizerUseLabels, _T("&Focused")); m_chkUseHover = CreateCheckBoxAndAddToSizer(sizerUseLabels, _T("&Hover")); m_chkUseDisabled = CreateCheckBoxAndAddToSizer(sizerUseLabels, _T("&Disabled")); sizerLeft->Add(sizerUseLabels, wxSizerFlags().Expand().Border()); + sizerLeft->AddSpacer(10); + + static const wxString dirs[] = + { + "left", "right", "top", "bottom", + }; + m_radioImagePos = new wxRadioBox(this, wxID_ANY, "Image &position", + wxDefaultPosition, wxDefaultSize, + WXSIZEOF(dirs), dirs); + sizerLeft->Add(m_radioImagePos, 0, wxGROW | wxALL, 5); sizerLeft->AddSpacer(15); // should be in sync with enums Button[HV]Align! @@ -293,6 +310,7 @@ void ButtonWidgetsPage::Reset() m_chkUseHover->SetValue(true); m_chkUseDisabled->SetValue(true); + m_radioImagePos->SetSelection(ButtonImagePos_Left); m_radioHAlign->SetSelection(ButtonHAlign_Centre); m_radioVAlign->SetSelection(ButtonVAlign_Centre); } @@ -385,12 +403,16 @@ void ButtonWidgetsPage::CreateButton() m_chkUseHover->Enable(isBitmapButton); m_chkUseDisabled->Enable(isBitmapButton); -#ifdef __WXUNIVERSAL__ if ( m_chkImage->GetValue() ) { - m_button->SetImageLabel(wxArtProvider::GetIcon(wxART_INFORMATION)); + static const wxDirection positions[] = + { + wxLEFT, wxRIGHT, wxTOP, wxBOTTOM + }; + + m_button->SetBitmap(wxArtProvider::GetIcon(wxART_INFORMATION), + positions[m_radioImagePos->GetSelection()]); } -#endif // wxUniv if ( m_chkDefault->GetValue() ) { diff --git a/src/common/btncmn.cpp b/src/common/btncmn.cpp index 2207057082..670dceacdf 100644 --- a/src/common/btncmn.cpp +++ b/src/common/btncmn.cpp @@ -44,4 +44,16 @@ wxWindow *wxButtonBase::SetDefault() return tlw->SetDefaultItem(this); } +void wxButtonBase::SetBitmapPosition(wxDirection dir) +{ + wxASSERT_MSG( !(dir & ~wxDIRECTION_MASK), "non-direction flag used" ); + wxASSERT_MSG( !!(dir & wxLEFT) + + !!(dir & wxRIGHT) + + !!(dir & wxTOP) + + !!(dir & wxBOTTOM) == 1, + "exactly one direction flag must be set" ); + + DoSetBitmapPosition(dir); + +} #endif // wxUSE_BUTTON diff --git a/src/msw/button.cpp b/src/msw/button.cpp index 305638ca4a..ead9214d2c 100644 --- a/src/msw/button.cpp +++ b/src/msw/button.cpp @@ -37,6 +37,7 @@ #include "wx/dcscreen.h" #include "wx/dcclient.h" #include "wx/toplevel.h" + #include "wx/imaglist.h" #endif #include "wx/stockitem.h" @@ -59,9 +60,26 @@ #define TMT_CONTENTMARGINS 3602 #endif + // provide the necessary declarations ourselves if they're missing from + // headers #ifndef BCM_SETIMAGELIST #define BCM_SETIMAGELIST 0x1602 #define BCM_SETTEXTMARGIN 0x1604 + + enum + { + BUTTON_IMAGELIST_ALIGN_LEFT, + BUTTON_IMAGELIST_ALIGN_RIGHT, + BUTTON_IMAGELIST_ALIGN_TOP, + BUTTON_IMAGELIST_ALIGN_BOTTOM + }; + + struct BUTTON_IMAGELIST + { + HIMAGELIST himl; + RECT margin; + UINT uAlign; + }; #endif #endif // wxUSE_UXTHEME @@ -81,6 +99,213 @@ #define DT_HIDEPREFIX 0x00100000 #endif +// ---------------------------------------------------------------------------- +// button image data +// ---------------------------------------------------------------------------- + +// we use different data classes for owner drawn buttons and for themed XP ones + +class wxButtonImageData +{ +public: + wxButtonImageData() { } + + virtual wxBitmap GetBitmap(wxButton::State which) const = 0; + virtual void SetBitmap(const wxBitmap& bitmap, wxButton::State which) = 0; + + virtual wxSize GetBitmapMargins() const = 0; + virtual void SetBitmapMargins(wxCoord x, wxCoord y) = 0; + + virtual bool IsHorizontal() const = 0; + virtual void SetBitmapPosition(wxDirection dir) = 0; + +private: + wxDECLARE_NO_COPY_CLASS(wxButtonImageData); +}; + +namespace +{ + +class wxODButtonImageData : public wxButtonImageData +{ +public: + wxODButtonImageData() { m_dir = wxLEFT; } + + virtual wxBitmap GetBitmap(wxButton::State which) const + { + return m_bitmaps[which]; + } + + virtual void SetBitmap(const wxBitmap& bitmap, wxButton::State which) + { + m_bitmaps[which] = bitmap; + } + + virtual wxSize GetBitmapMargins() const + { + return m_margin; + } + + virtual void SetBitmapMargins(wxCoord x, wxCoord y) + { + m_margin = wxSize(x, y); + } + + virtual bool IsHorizontal() const + { + return m_dir == wxLEFT || m_dir == wxRIGHT; + } + + virtual void SetBitmapPosition(wxDirection dir) + { + m_dir = dir; + } + +private: + // just store the values passed to us to be able to retrieve them later + // from the drawing code + wxBitmap m_bitmaps[wxButton::State_Max]; + wxSize m_margin; + wxDirection m_dir; + + wxDECLARE_NO_COPY_CLASS(wxODButtonImageData); +}; + +#if wxUSE_UXTHEME + +class wxXPButtonImageData : public wxButtonImageData +{ +public: + // we must be constructed with the size of our images as we need to create + // the image list + wxXPButtonImageData(wxButton *btn, const wxSize& size) + : m_iml(size.x, size.y, true /* use mask */, wxButton::State_Max), + m_hwndBtn(GetHwndOf(btn)) + { + m_data.himl = GetHimagelistOf(&m_iml); + + // use default margins + m_data.margin.left = + m_data.margin.right = btn->GetCharWidth(); + m_data.margin.top = + m_data.margin.bottom = btn->GetCharHeight() / 2; + + // and default alignment + m_data.uAlign = BUTTON_IMAGELIST_ALIGN_LEFT; + } + + virtual wxBitmap GetBitmap(wxButton::State which) const + { + return m_iml.GetBitmap(which); + } + + virtual void SetBitmap(const wxBitmap& bitmap, wxButton::State which) + { + const int imagesToAdd = which - m_iml.GetImageCount(); + if ( imagesToAdd >= 0 ) + { + if ( imagesToAdd > 0 ) + { + const wxBitmap bmpNormal = GetBitmap(wxButton::State_Normal); + for ( int n = 0; n < imagesToAdd; n++ ) + m_iml.Add(bmpNormal); + } + + m_iml.Add(bitmap); + } + else // we already have this bitmap + { + m_iml.Replace(which, bitmap); + } + + UpdateImageInfo(); + } + + virtual wxSize GetBitmapMargins() const + { + return wxSize(m_data.margin.left, m_data.margin.top); + } + + virtual void SetBitmapMargins(wxCoord x, wxCoord y) + { + RECT& margin = m_data.margin; + margin.left = + margin.right = x; + margin.top = + margin.bottom = y; + + if ( !::SendMessage(m_hwndBtn, BCM_SETTEXTMARGIN, 0, (LPARAM)&margin) ) + { + wxLogDebug("SendMessage(BCM_SETTEXTMARGIN) failed"); + } + } + + virtual bool IsHorizontal() const + { + return m_data.uAlign == BUTTON_IMAGELIST_ALIGN_LEFT || + m_data.uAlign == BUTTON_IMAGELIST_ALIGN_RIGHT; + } + + virtual void SetBitmapPosition(wxDirection dir) + { + UINT alignNew; + switch ( dir ) + { + default: + wxFAIL_MSG( "invalid direction" ); + // fall through + + case wxLEFT: + alignNew = BUTTON_IMAGELIST_ALIGN_LEFT; + break; + + case wxRIGHT: + alignNew = BUTTON_IMAGELIST_ALIGN_RIGHT; + break; + + case wxTOP: + alignNew = BUTTON_IMAGELIST_ALIGN_TOP; + break; + + case wxBOTTOM: + alignNew = BUTTON_IMAGELIST_ALIGN_BOTTOM; + break; + } + + if ( alignNew != m_data.uAlign ) + { + m_data.uAlign = alignNew; + UpdateImageInfo(); + } + } + +private: + void UpdateImageInfo() + { + if ( !::SendMessage(m_hwndBtn, BCM_SETIMAGELIST, 0, (LPARAM)&m_data) ) + { + wxLogDebug("SendMessage(BCM_SETIMAGELIST) failed"); + } + } + + // we store image list separately to be able to use convenient wxImageList + // methods instead of working with raw HIMAGELIST + wxImageList m_iml; + + // store the rest of the data in BCM_SETIMAGELIST-friendly form + BUTTON_IMAGELIST m_data; + + // the button we're associated with + const HWND m_hwndBtn; + + + wxDECLARE_NO_COPY_CLASS(wxXPButtonImageData); +}; + +#endif // wxUSE_UXTHEME + +} // anonymous namespace + // ---------------------------------------------------------------------------- // macros // ---------------------------------------------------------------------------- @@ -259,6 +484,8 @@ wxButton::~wxButton() { UnsetTmpDefault(); } + + delete m_imageData; } // ---------------------------------------------------------------------------- @@ -310,7 +537,29 @@ void wxButton::SetLabel(const wxString& label) wxSize wxButton::DoGetBestSize() const { - return wxMSWButton::ComputeBestSize(const_cast(this)); + wxSize size = wxMSWButton::ComputeBestSize(const_cast(this)); + if ( m_imageData ) + { + const wxSize sizeBmp = m_imageData->GetBitmap(State_Normal).GetSize(); + if ( m_imageData->IsHorizontal() ) + { + size.x += sizeBmp.x; + if ( sizeBmp.y > size.y ) + size.y = sizeBmp.y; + } + else // bitmap on top/below the text + { + size.y += sizeBmp.y; + if ( sizeBmp.x > size.x ) + size.x = sizeBmp.x; + } + + size += 2*m_imageData->GetBitmapMargins(); + + CacheBestSize(size); + } + + return size; } /* static */ @@ -590,6 +839,49 @@ WXLRESULT wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) return wxControl::MSWWindowProc(nMsg, wParam, lParam); } +// ---------------------------------------------------------------------------- +// button images +// ---------------------------------------------------------------------------- + +wxBitmap wxButton::DoGetBitmap(State which) const +{ + return m_imageData ? m_imageData->GetBitmap(which) : wxBitmap(); +} + +void wxButton::DoSetBitmap(const wxBitmap& bitmap, State which) +{ + // allocate the image data when the first bitmap is set + if ( !m_imageData ) + { +#if wxUSE_UXTHEME + if ( wxUxThemeEngine::GetIfActive() ) + m_imageData = new wxXPButtonImageData(this, bitmap.GetSize()); + else +#endif // wxUSE_UXTHEME + m_imageData = new wxODButtonImageData; + + // if a bitmap was assigned to the bitmap, its best size must be + // changed to account for it + InvalidateBestSize(); + } + + m_imageData->SetBitmap(bitmap, which); +} + +void wxButton::DoSetBitmapMargins(wxCoord x, wxCoord y) +{ + wxCHECK_RET( m_imageData, "SetBitmap() must be called first" ); + + m_imageData->SetBitmapMargins(x, y); +} + +void wxButton::DoSetBitmapPosition(wxDirection dir) +{ + wxCHECK_RET( m_imageData, "SetBitmap() must be called first" ); + + m_imageData->SetBitmapPosition(dir); +} + // ---------------------------------------------------------------------------- // owner-drawn buttons support // ---------------------------------------------------------------------------- -- 2.49.0