]> git.saurik.com Git - wxWidgets.git/commitdiff
implement images support for wxButton under XP and later
authorVadim Zeitlin <vadim@wxwidgets.org>
Mon, 15 Jun 2009 04:23:54 +0000 (04:23 +0000)
committerVadim Zeitlin <vadim@wxwidgets.org>
Mon, 15 Jun 2009 04:23:54 +0000 (04:23 +0000)
git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@61054 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775

include/wx/button.h
include/wx/msw/button.h
samples/widgets/button.cpp
src/common/btncmn.cpp
src/msw/button.cpp

index 9feabd6e1cda3a3fab136a17ce52903cc4894cf0..e2d3dbcd85c72b78acda91f3e8c028cee374b83c 100644 (file)
@@ -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);
index 1ee21c4d1e27d4822580c37159386e7b5e9f88e8..19d204f2e14db28cfb22d5e01da2b2caed6fa663 100644 (file)
@@ -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)
 };
index 914fa1a08b347d7eb210c85dcb5ed7d41d34b660..c01bc1cd276af77ba8a348e3909ce3ac1ffdc79f 100644 (file)
@@ -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() )
     {
index 2207057082642a08cd12d213b9d7563f03b79b94..670dceacdf4963615f7a08578f2aa0a9b8b106d2 100644 (file)
@@ -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
index 305638ca4ac7a72db3c51dd1923770ef21c20348..ead9214d2cd451282b93066b7709a45fc98a0b1c 100644 (file)
@@ -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"
         #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
 
     #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<wxButton *>(this));
+    wxSize size = wxMSWButton::ComputeBestSize(const_cast<wxButton *>(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
 // ----------------------------------------------------------------------------