X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/c87fc2859df7329be3cf9484d9e27bc59dc63ea9..224d978ffb483f93864f8bf9a5cd61b1425a7fd5:/src/msw/button.cpp diff --git a/src/msw/button.cpp b/src/msw/button.cpp index 0c07934f05..67494356d6 100644 --- a/src/msw/button.cpp +++ b/src/msw/button.cpp @@ -1,5 +1,5 @@ ///////////////////////////////////////////////////////////////////////////// -// Name: msw/button.cpp +// Name: src/msw/button.cpp // Purpose: wxButton // Author: Julian Smart // Modified by: @@ -17,10 +17,6 @@ // headers // ---------------------------------------------------------------------------- -#if defined(__GNUG__) && !defined(NO_GCC_PRAGMA) - #pragma implementation "button.h" -#endif - // For compilers that support precompilation, includes "wx.h". #include "wx/wxprec.h" @@ -30,19 +26,320 @@ #if wxUSE_BUTTON +#include "wx/button.h" + #ifndef WX_PRECOMP #include "wx/app.h" - #include "wx/button.h" #include "wx/brush.h" #include "wx/panel.h" #include "wx/bmpbuttn.h" #include "wx/settings.h" #include "wx/dcscreen.h" + #include "wx/dcclient.h" + #include "wx/toplevel.h" #endif +#include "wx/imaglist.h" #include "wx/stockitem.h" -#include "wx/tokenzr.h" #include "wx/msw/private.h" +#include "wx/msw/private/button.h" +#include "wx/msw/private/dc.h" + +using namespace wxMSWImpl; + +#if wxUSE_UXTHEME + #include "wx/msw/uxtheme.h" + + // no need to include tmschema.h + #ifndef BP_PUSHBUTTON + #define BP_PUSHBUTTON 1 + + #define PBS_NORMAL 1 + #define PBS_HOT 2 + #define PBS_PRESSED 3 + #define PBS_DISABLED 4 + #define PBS_DEFAULTED 5 + + #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 + +#ifndef WM_THEMECHANGED + #define WM_THEMECHANGED 0x031A +#endif + +#ifndef ODS_NOACCEL + #define ODS_NOACCEL 0x0100 +#endif + +#ifndef ODS_NOFOCUSRECT + #define ODS_NOFOCUSRECT 0x0200 +#endif + +#ifndef DT_HIDEPREFIX + #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 ~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 wxDirection GetBitmapPosition() const = 0; + virtual void SetBitmapPosition(wxDirection dir) = 0; + +private: + wxDECLARE_NO_COPY_CLASS(wxButtonImageData); +}; + +namespace +{ + +// the gap between button edge and the interior area used by Windows for the +// standard buttons +const int OD_BUTTON_MARGIN = 4; + +class wxODButtonImageData : public wxButtonImageData +{ +public: + wxODButtonImageData(wxButton *btn, const wxBitmap& bitmap) + { + SetBitmap(bitmap, wxButton::State_Normal); + + m_dir = wxLEFT; + + // we use margins when we have both bitmap and text, but when we have + // only the bitmap it should take up the entire button area + if ( btn->ShowsLabel() ) + { + m_margin.x = btn->GetCharWidth(); + m_margin.y = btn->GetCharHeight() / 2; + } + } + + 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 wxDirection GetBitmapPosition() const + { + return m_dir; + } + + 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 + +// somehow the margin is one pixel greater than the value returned by +// GetThemeMargins() call +const int XP_BUTTON_EXTRA_MARGIN = 1; + +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 wxBitmap& bitmap) + : m_iml(bitmap.GetWidth(), bitmap.GetHeight(), true /* use mask */, + wxButton::State_Max), + m_hwndBtn(GetHwndOf(btn)) + { + // initialize all bitmaps to normal state + for ( int n = 0; n < wxButton::State_Max; n++ ) + { + m_iml.Add(bitmap); + } + + 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; + + UpdateImageInfo(); + } + + virtual wxBitmap GetBitmap(wxButton::State which) const + { + return m_iml.GetBitmap(which); + } + + virtual void SetBitmap(const wxBitmap& bitmap, wxButton::State which) + { + 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 wxDirection GetBitmapPosition() const + { + switch ( m_data.uAlign ) + { + default: + wxFAIL_MSG( "invalid image alignment" ); + // fall through + + case BUTTON_IMAGELIST_ALIGN_LEFT: + return wxLEFT; + + case BUTTON_IMAGELIST_ALIGN_RIGHT: + return wxRIGHT; + + case BUTTON_IMAGELIST_ALIGN_TOP: + return wxTOP; + + case BUTTON_IMAGELIST_ALIGN_BOTTOM: + return wxBOTTOM; + } + } + + 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 @@ -109,14 +406,69 @@ wxCONSTRUCTOR_6( wxButton , wxWindow* , Parent , wxWindowID , Id , wxString , La IMPLEMENT_DYNAMIC_CLASS(wxButton, wxControl) #endif -// this macro tries to adjust the default button height to a reasonable value -// using the char height as the base -#define BUTTON_HEIGHT_FROM_CHAR_HEIGHT(cy) (11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(cy)/10) - // ============================================================================ // implementation // ============================================================================ +// ---------------------------------------------------------------------------- +// helper functions from wx/msw/private/button.h +// ---------------------------------------------------------------------------- + +void wxMSWButton::UpdateMultilineStyle(HWND hwnd, const wxString& label) +{ + // update BS_MULTILINE style depending on the new label (resetting it + // doesn't seem to do anything very useful but it shouldn't hurt and we do + // have to set it whenever the label becomes multi line as otherwise it + // wouldn't be shown correctly as we don't use BS_MULTILINE when creating + // the control unless it already has new lines in its label) + long styleOld = ::GetWindowLong(hwnd, GWL_STYLE), + styleNew; + if ( label.find(wxT('\n')) != wxString::npos ) + styleNew = styleOld | BS_MULTILINE; + else + styleNew = styleOld & ~BS_MULTILINE; + + if ( styleNew != styleOld ) + ::SetWindowLong(hwnd, GWL_STYLE, styleNew); +} + +wxSize wxMSWButton::GetFittingSize(wxWindow *win, const wxSize& sizeLabel) +{ + // FIXME: this is pure guesswork, need to retrieve the real button margins + wxSize sizeBtn = sizeLabel; + + sizeBtn.x += 3*win->GetCharWidth(); + sizeBtn.y = 11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(sizeLabel.y)/10; + + return sizeBtn; +} + +wxSize wxMSWButton::ComputeBestSize(wxControl *btn) +{ + wxClientDC dc(btn); + + wxSize sizeBtn; + dc.GetMultiLineTextExtent(btn->GetLabelText(), &sizeBtn.x, &sizeBtn.y); + + sizeBtn = GetFittingSize(btn, sizeBtn); + + // all buttons have at least the standard size unless the user explicitly + // wants them to be of smaller size and used wxBU_EXACTFIT style when + // creating the button + if ( !btn->HasFlag(wxBU_EXACTFIT) ) + { + wxSize sizeDef = wxButton::GetDefaultSize(); + if ( sizeBtn.x < sizeDef.x ) + sizeBtn.x = sizeDef.x; + if ( sizeBtn.y < sizeDef.y ) + sizeBtn.y = sizeDef.y; + } + + btn->CacheBestSize(sizeBtn); + + return sizeBtn; +} + // ---------------------------------------------------------------------------- // creation/destruction // ---------------------------------------------------------------------------- @@ -133,43 +485,42 @@ bool wxButton::Create(wxWindow *parent, wxString label(lbl); if (label.empty() && wxIsStockID(id)) { - // On Windows, some buttons aren't supposed to have - // mnemonics, so strip them out. - - label = wxGetStockLabel(id -#if defined(__WXMSW__) || defined(__WXWINCE__) - , ( id != wxID_OK && - id != wxID_CANCEL && - id != wxID_CLOSE ) -#endif - ); - } - + // On Windows, some buttons aren't supposed to have mnemonics + label = wxGetStockLabel + ( + id, + id == wxID_OK || id == wxID_CANCEL || id == wxID_CLOSE + ? wxSTOCK_NOFLAGS + : wxSTOCK_WITH_MNEMONIC + ); + } + if ( !CreateControl(parent, id, pos, size, style, validator, name) ) return false; WXDWORD exstyle; WXDWORD msStyle = MSWGetStyle(style, &exstyle); -#ifdef __WIN32__ // if the label contains several lines we must explicitly tell the button // about it or it wouldn't draw it correctly ("\n"s would just appear as // black boxes) // // NB: we do it here and not in MSWGetStyle() because we need the label - // value and m_label is not set yet when MSWGetStyle() is called; - // besides changing BS_MULTILINE during run-time is pointless anyhow - if ( label.find(_T('\n')) != wxString::npos ) - { - msStyle |= BS_MULTILINE; - } -#endif // __WIN32__ + // value and the label is not set yet when MSWGetStyle() is called + msStyle |= wxMSWButton::GetMultilineStyle(label); - return MSWCreateControl(_T("BUTTON"), msStyle, pos, size, label, exstyle); + return MSWCreateControl(wxT("BUTTON"), msStyle, pos, size, label, exstyle); } wxButton::~wxButton() { + wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow); + if ( tlw && tlw->GetTmpDefaultItem() == this ) + { + UnsetTmpDefault(); + } + + delete m_imageData; } // ---------------------------------------------------------------------------- @@ -189,7 +540,6 @@ WXDWORD wxButton::MSWGetStyle(long style, WXDWORD *exstyle) const // the bottom msStyle |= WS_CLIPSIBLINGS; -#ifdef __WIN32__ // don't use "else if" here: weird as it is, but you may combine wxBU_LEFT // and wxBU_RIGHT to get BS_CENTER! if ( style & wxBU_LEFT ) @@ -205,65 +555,98 @@ WXDWORD wxButton::MSWGetStyle(long style, WXDWORD *exstyle) const if ( style & wxNO_BORDER ) msStyle |= BS_FLAT; #endif // __WXWINCE__ -#endif // __WIN32__ return msStyle; } +void wxButton::SetLabel(const wxString& label) +{ + wxMSWButton::UpdateMultilineStyle(GetHwnd(), label); + + wxButtonBase::SetLabel(label); +} + // ---------------------------------------------------------------------------- // size management including autosizing // ---------------------------------------------------------------------------- wxSize wxButton::DoGetBestSize() const { - int wBtn = 0; - int wChar, hChar, hBtn; - wxGetCharSize(GetHWND(), &wChar, &hChar, GetFont()); + wxSize size; - wxString label = wxGetWindowText(GetHWND()); - if ( label.find(_T('\n')) != wxString::npos ) + // account for the text part if we have it or if we don't have any image at + // all (buttons initially created with empty label should still have a non + // zero size) + if ( ShowsLabel() || !m_imageData ) { - wxStringTokenizer tokens( label, wxT("\n") ); + size = wxMSWButton::ComputeBestSize(const_cast(this)); + } - // the button height is proportional to the height of the font used - hBtn = BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hChar); - hBtn += hChar*(tokens.CountTokens()-1); - - while (tokens.HasMoreTokens()) + if ( m_imageData ) + { + // account for the bitmap size + const wxSize sizeBmp = m_imageData->GetBitmap(State_Normal).GetSize(); + const wxDirection dirBmp = m_imageData->GetBitmapPosition(); + if ( dirBmp == wxLEFT || dirBmp == wxRIGHT ) { - wxString sub = tokens.GetNextToken(); - int w; - GetTextExtent( sub, &w, NULL); - if (w > wBtn) - wBtn = w; + 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; } - } - else - { - GetTextExtent( label, &wBtn, NULL); - // the button height is proportional to the height of the font used - hBtn = BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hChar); - } - - // add a margin -- the button is wider than just its label - wBtn += 3*wChar; + // account for the user-specified margins + size += 2*m_imageData->GetBitmapMargins(); - // all buttons have at least the standard size unless the user explicitly - // wants them to be of smaller size and used wxBU_EXACTFIT style when - // creating the button - if ( !HasFlag(wxBU_EXACTFIT) ) - { - wxSize sz = GetDefaultSize(); - if (wBtn > sz.x) - sz.x = wBtn; - if (hBtn > sz.y) - sz.y = hBtn; + // and also for the margins we always add internally (unless we have no + // border at all in which case the button has exactly the same size as + // bitmap and so no margins should be used) + if ( !HasFlag(wxBORDER_NONE) ) + { + int marginH = 0, + marginV = 0; +#if wxUSE_UXTHEME + if ( wxUxThemeEngine::GetIfActive() ) + { + wxUxThemeHandle theme(const_cast(this), L"BUTTON"); + + MARGINS margins; + wxUxThemeEngine::Get()->GetThemeMargins(theme, NULL, + BP_PUSHBUTTON, + PBS_NORMAL, + TMT_CONTENTMARGINS, + NULL, + &margins); + + // XP doesn't draw themed buttons correctly when the client + // area is smaller than 8x8 - enforce this minimum size for + // small bitmaps + size.IncTo(wxSize(8, 8)); + + marginH = margins.cxLeftWidth + margins.cxRightWidth + + 2*XP_BUTTON_EXTRA_MARGIN; + marginV = margins.cyTopHeight + margins.cyBottomHeight + + 2*XP_BUTTON_EXTRA_MARGIN; + } + else +#endif // wxUSE_UXTHEME + { + marginH = + marginV = OD_BUTTON_MARGIN; + } + + size.IncBy(marginH, marginV); + } - return sz; + CacheBestSize(size); } - return wxSize(wBtn, hBtn); + return size; } /* static */ @@ -296,6 +679,14 @@ wxSize wxButtonBase::GetDefaultSize() // ---------------------------------------------------------------------------- /* + The comment below and all this code is probably due to not using WM_NEXTDLGCTL + message when changing focus (but just SetFocus() which is not enough), see + http://blogs.msdn.com/oldnewthing/archive/2004/08/02/205624.aspx for the + full explanation. + + TODO: Do use WM_NEXTDLGCTL and get rid of all this code. + + "Everything you ever wanted to know about the default buttons" or "Why do we have to do all this?" @@ -330,29 +721,55 @@ wxSize wxButtonBase::GetDefaultSize() */ // set this button as the (permanently) default one in its panel -void wxButton::SetDefault() +wxWindow *wxButton::SetDefault() { - wxWindow *parent = GetParent(); - - wxCHECK_RET( parent, _T("button without parent?") ); - // set this one as the default button both for wxWidgets ... - wxWindow *winOldDefault = parent->SetDefaultItem(this); + wxWindow *winOldDefault = wxButtonBase::SetDefault(); // ... and Windows SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), false); SetDefaultStyle(this, true); + + return winOldDefault; +} + +// return the top level parent window if it's not being deleted yet, otherwise +// return NULL +static wxTopLevelWindow *GetTLWParentIfNotBeingDeleted(wxWindow *win) +{ + for ( ;; ) + { + // IsTopLevel() will return false for a wxTLW being deleted, so we also + // need the parent test for this case + wxWindow * const parent = win->GetParent(); + if ( !parent || win->IsTopLevel() ) + { + if ( win->IsBeingDeleted() ) + return NULL; + + break; + } + + win = parent; + } + + wxASSERT_MSG( win, wxT("button without top level parent?") ); + + wxTopLevelWindow * const tlw = wxDynamicCast(win, wxTopLevelWindow); + wxASSERT_MSG( tlw, wxT("logic error in GetTLWParentIfNotBeingDeleted()") ); + + return tlw; } // set this button as being currently default void wxButton::SetTmpDefault() { - wxWindow *parent = GetParent(); - - wxCHECK_RET( parent, _T("button without parent?") ); + wxTopLevelWindow * const tlw = GetTLWParentIfNotBeingDeleted(GetParent()); + if ( !tlw ) + return; - wxWindow *winOldDefault = parent->GetDefaultItem(); - parent->SetTmpDefaultItem(this); + wxWindow *winOldDefault = tlw->GetDefaultItem(); + tlw->SetTmpDefaultItem(this); SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), false); SetDefaultStyle(this, true); @@ -361,13 +778,13 @@ void wxButton::SetTmpDefault() // unset this button as currently default, it may still stay permanent default void wxButton::UnsetTmpDefault() { - wxWindow *parent = GetParent(); - - wxCHECK_RET( parent, _T("button without parent?") ); + wxTopLevelWindow * const tlw = GetTLWParentIfNotBeingDeleted(GetParent()); + if ( !tlw ) + return; - parent->SetTmpDefaultItem(NULL); + tlw->SetTmpDefaultItem(NULL); - wxWindow *winOldDefault = parent->GetDefaultItem(); + wxWindow *winOldDefault = tlw->GetDefaultItem(); SetDefaultStyle(this, false); SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), true); @@ -390,18 +807,13 @@ wxButton::SetDefaultStyle(wxButton *btn, bool on) if ( !wxTheApp->IsActive() ) return; - // look for a panel-like window - wxWindow *win = btn->GetParent(); - while ( win && !win->HasFlag(wxTAB_TRAVERSAL) ) - win = win->GetParent(); + wxWindow * const tlw = wxGetTopLevelParent(btn); + wxCHECK_RET( tlw, wxT("button without top level window?") ); - if ( win ) - { - ::SendMessage(GetHwndOf(win), DM_SETDEFID, btn->GetId(), 0L); + ::SendMessage(GetHwndOf(tlw), DM_SETDEFID, btn->GetId(), 0L); - // sending DM_SETDEFID also changes the button style to - // BS_DEFPUSHBUTTON so there is nothing more to do - } + // sending DM_SETDEFID also changes the button style to + // BS_DEFPUSHBUTTON so there is nothing more to do } // then also change the style as needed @@ -496,81 +908,175 @@ WXLRESULT wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) // and continue with processing the message normally as well } +#if wxUSE_UXTHEME + else if ( nMsg == WM_THEMECHANGED ) + { + // need to recalculate the best size here + // as the theme size might have changed + InvalidateBestSize(); + } +#endif // wxUSE_UXTHEME + // must use m_mouseInWindow here instead of IsMouseInWindow() + // since we need to know the first time the mouse enters the window + // and IsMouseInWindow() would return true in this case + else if ( (nMsg == WM_MOUSEMOVE && !m_mouseInWindow) || + nMsg == WM_MOUSELEAVE ) + { + if ( + IsEnabled() && + ( +#if wxUSE_UXTHEME + wxUxThemeEngine::GetIfActive() || +#endif // wxUSE_UXTHEME + m_imageData && m_imageData->GetBitmap(State_Current).IsOk() + ) + ) + { + Refresh(); + } + } // let the base class do all real processing return wxControl::MSWWindowProc(nMsg, wParam, lParam); } // ---------------------------------------------------------------------------- -// owner-drawn buttons support +// button images // ---------------------------------------------------------------------------- -#ifdef __WIN32__ - -// drawing helpers +wxBitmap wxButton::DoGetBitmap(State which) const +{ + return m_imageData ? m_imageData->GetBitmap(which) : wxBitmap(); +} -static void DrawButtonText(HDC hdc, - RECT *pRect, - const wxString& text, - COLORREF col) +void wxButton::DoSetBitmap(const wxBitmap& bitmap, State which) { - COLORREF colOld = SetTextColor(hdc, col); - int modeOld = SetBkMode(hdc, TRANSPARENT); + // allocate the image data when the first bitmap is set + if ( !m_imageData ) + { +#if wxUSE_UXTHEME + // using image list doesn't work correctly if we don't have any label + // (even if we use BUTTON_IMAGELIST_ALIGN_CENTER alignment and + // BS_BITMAP style), at least under Windows 2003 so use owner drawn + // strategy for bitmap-only buttons + if ( ShowsLabel() && wxUxThemeEngine::GetIfActive() ) + { + m_imageData = new wxXPButtonImageData(this, bitmap); + } + else +#endif // wxUSE_UXTHEME + { + m_imageData = new wxODButtonImageData(this, bitmap); + MakeOwnerDrawn(); + } + } + else + { + m_imageData->SetBitmap(bitmap, which); + } - // Note: we must have DT_SINGLELINE for DT_VCENTER to work. - ::DrawText(hdc, text, text.length(), pRect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + // it should be enough to only invalidate the best size when the normal + // bitmap changes as all bitmaps assigned to the button should be of the + // same size anyhow + if ( which == State_Normal ) + InvalidateBestSize(); - SetBkMode(hdc, modeOld); - SetTextColor(hdc, colOld); + Refresh(); } -static void DrawRect(HDC hdc, const RECT& r) +wxSize wxButton::DoGetBitmapMargins() const { - wxDrawLine(hdc, r.left, r.top, r.right, r.top); - wxDrawLine(hdc, r.right, r.top, r.right, r.bottom); - wxDrawLine(hdc, r.right, r.bottom, r.left, r.bottom); - wxDrawLine(hdc, r.left, r.bottom, r.left, r.top); + return m_imageData ? m_imageData->GetBitmapMargins() : wxSize(0, 0); } -void wxButton::MakeOwnerDrawn() +void wxButton::DoSetBitmapMargins(wxCoord x, wxCoord y) { - long style = GetWindowLong(GetHwnd(), GWL_STYLE); - if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW ) - { - // make it so - style |= BS_OWNERDRAW; - SetWindowLong(GetHwnd(), GWL_STYLE, style); - } + wxCHECK_RET( m_imageData, "SetBitmap() must be called first" ); + + m_imageData->SetBitmapMargins(x, y); } -bool wxButton::SetBackgroundColour(const wxColour &colour) +void wxButton::DoSetBitmapPosition(wxDirection dir) { - if ( !wxControl::SetBackgroundColour(colour) ) - { - // nothing to do - return false; - } + wxCHECK_RET( m_imageData, "SetBitmap() must be called first" ); - MakeOwnerDrawn(); + m_imageData->SetBitmapPosition(dir); +} - Refresh(); +// ---------------------------------------------------------------------------- +// owner-drawn buttons support +// ---------------------------------------------------------------------------- - return true; +// drawing helpers +namespace +{ + +// return the button state using both the ODS_XXX flags specified in state +// parameter and the current button state +wxButton::State GetButtonState(wxButton *btn, UINT state) +{ + if ( state & ODS_DISABLED ) + return wxButton::State_Disabled; + + if ( state & ODS_SELECTED ) + return wxButton::State_Pressed; + + if ( btn->HasCapture() || btn->IsMouseInWindow() ) + return wxButton::State_Current; + + if ( state & ODS_FOCUS ) + return wxButton::State_Focused; + + return wxButton::State_Normal; } -bool wxButton::SetForegroundColour(const wxColour &colour) +void DrawButtonText(HDC hdc, + RECT *pRect, + const wxString& text, + COLORREF col, + int flags) { - if ( !wxControl::SetForegroundColour(colour) ) - { - // nothing to do - return false; - } + wxTextColoursChanger changeFg(hdc, col, CLR_INVALID); + wxBkModeChanger changeBkMode(hdc, wxBRUSHSTYLE_TRANSPARENT); - MakeOwnerDrawn(); + // center text horizontally in any case + flags |= DT_CENTER; - Refresh(); + if ( text.find(wxT('\n')) != wxString::npos ) + { + // draw multiline label + + // first we need to compute its bounding rect + RECT rc; + ::CopyRect(&rc, pRect); + ::DrawText(hdc, text.wx_str(), text.length(), &rc, + DT_CENTER | DT_CALCRECT); + + // now center this rect inside the entire button area + const LONG w = rc.right - rc.left; + const LONG h = rc.bottom - rc.top; + rc.left = (pRect->right - pRect->left)/2 - w/2; + rc.right = rc.left+w; + rc.top = (pRect->bottom - pRect->top)/2 - h/2; + rc.bottom = rc.top+h; + + ::DrawText(hdc, text.wx_str(), text.length(), &rc, flags); + } + else // single line label + { + // centre text vertically too (notice that we must have DT_SINGLELINE + // for DT_VCENTER to work) + ::DrawText(hdc, text.wx_str(), text.length(), pRect, + flags | DT_SINGLELINE | DT_VCENTER); + } +} - return true; +void DrawRect(HDC hdc, const RECT& r) +{ + wxDrawLine(hdc, r.left, r.top, r.right, r.top); + wxDrawLine(hdc, r.right, r.top, r.right, r.bottom); + wxDrawLine(hdc, r.right, r.bottom, r.left, r.bottom); + wxDrawLine(hdc, r.left, r.bottom, r.left, r.top); } /* @@ -608,19 +1114,18 @@ bool wxButton::SetForegroundColour(const wxColour &colour) BGGGGGGGGGGGGGGGGGB BBBBBBBBBBBBBBBBBBB */ - -static void DrawButtonFrame(HDC hdc, const RECT& rectBtn, - bool selected, bool pushed) +void DrawButtonFrame(HDC hdc, RECT& rectBtn, + bool selected, bool pushed) { RECT r; CopyRect(&r, &rectBtn); - HPEN hpenBlack = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DDKSHADOW)), - hpenGrey = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DSHADOW)), - hpenLightGr = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DLIGHT)), - hpenWhite = CreatePen(PS_SOLID, 1, GetSysColor(COLOR_3DHILIGHT)); + AutoHPEN hpenBlack(GetSysColor(COLOR_3DDKSHADOW)), + hpenGrey(GetSysColor(COLOR_3DSHADOW)), + hpenLightGr(GetSysColor(COLOR_3DLIGHT)), + hpenWhite(GetSysColor(COLOR_3DHILIGHT)); - HPEN hpenOld = (HPEN)SelectObject(hdc, hpenBlack); + SelectInHDC selectPen(hdc, hpenBlack); r.right--; r.bottom--; @@ -659,74 +1164,237 @@ static void DrawButtonFrame(HDC hdc, const RECT& rectBtn, wxDrawLine(hdc, r.right - 1, r.bottom - 1, r.right - 1, r.top); } - (void)SelectObject(hdc, hpenOld); - DeleteObject(hpenWhite); - DeleteObject(hpenLightGr); - DeleteObject(hpenGrey); - DeleteObject(hpenBlack); + InflateRect(&rectBtn, -OD_BUTTON_MARGIN, -OD_BUTTON_MARGIN); } -bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis) +#if wxUSE_UXTHEME +void DrawXPBackground(wxButton *button, HDC hdc, RECT& rectBtn, UINT state) { - LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis; + wxUxThemeHandle theme(button, L"BUTTON"); - RECT rectBtn; - CopyRect(&rectBtn, &lpDIS->rcItem); + // this array is indexed by wxButton::State values and so must be kept in + // sync with it + static const int uxStates[] = + { + PBS_NORMAL, PBS_HOT, PBS_PRESSED, PBS_DISABLED, PBS_DEFAULTED + }; + + int iState = uxStates[GetButtonState(button, state)]; + + wxUxThemeEngine * const engine = wxUxThemeEngine::Get(); + + // draw parent background if needed + if ( engine->IsThemeBackgroundPartiallyTransparent + ( + theme, + BP_PUSHBUTTON, + iState + ) ) + { + engine->DrawThemeParentBackground(GetHwndOf(button), hdc, &rectBtn); + } + + // draw background + engine->DrawThemeBackground(theme, hdc, BP_PUSHBUTTON, iState, + &rectBtn, NULL); + + // calculate content area margins + MARGINS margins; + engine->GetThemeMargins(theme, hdc, BP_PUSHBUTTON, iState, + TMT_CONTENTMARGINS, &rectBtn, &margins); + ::InflateRect(&rectBtn, -margins.cxLeftWidth, -margins.cyTopHeight); + ::InflateRect(&rectBtn, -XP_BUTTON_EXTRA_MARGIN, -XP_BUTTON_EXTRA_MARGIN); + + if ( button->UseBgCol() ) + { + COLORREF colBg = wxColourToRGB(button->GetBackgroundColour()); + AutoHBRUSH hbrushBackground(colBg); + + // don't overwrite the focus rect + RECT rectClient; + ::CopyRect(&rectClient, &rectBtn); + ::InflateRect(&rectClient, -1, -1); + FillRect(hdc, &rectClient, hbrushBackground); + } +} +#endif // wxUSE_UXTHEME - COLORREF colBg = wxColourToRGB(GetBackgroundColour()), - colFg = wxColourToRGB(GetForegroundColour()); +} // anonymous namespace + +// ---------------------------------------------------------------------------- +// owner drawn buttons support +// ---------------------------------------------------------------------------- + +void wxButton::MakeOwnerDrawn() +{ + long style = GetWindowLong(GetHwnd(), GWL_STYLE); + if ( (style & BS_OWNERDRAW) != BS_OWNERDRAW ) + { + // make it so + style |= BS_OWNERDRAW; + SetWindowLong(GetHwnd(), GWL_STYLE, style); + } +} + +bool wxButton::SetBackgroundColour(const wxColour &colour) +{ + if ( !wxControl::SetBackgroundColour(colour) ) + { + // nothing to do + return false; + } + MakeOwnerDrawn(); + + Refresh(); + + return true; +} + +bool wxButton::SetForegroundColour(const wxColour &colour) +{ + if ( !wxControl::SetForegroundColour(colour) ) + { + // nothing to do + return false; + } + + MakeOwnerDrawn(); + + Refresh(); + + return true; +} + +bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis) +{ + LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis; HDC hdc = lpDIS->hDC; - UINT state = lpDIS->itemState; - // first, draw the background - HBRUSH hbrushBackground = ::CreateSolidBrush(colBg); + UINT state = lpDIS->itemState; + bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0; - FillRect(hdc, &rectBtn, hbrushBackground); + RECT rectBtn; + CopyRect(&rectBtn, &lpDIS->rcItem); - // draw the border for the current state - bool selected = (state & ODS_SELECTED) != 0; - if ( !selected ) + // draw the button background + if ( !HasFlag(wxBORDER_NONE) ) { - wxPanel *panel = wxDynamicCast(GetParent(), wxPanel); - if ( panel ) +#if wxUSE_UXTHEME + if ( wxUxThemeEngine::GetIfActive() ) + { + DrawXPBackground(this, hdc, rectBtn, state); + } + else +#endif // wxUSE_UXTHEME + { + COLORREF colBg = wxColourToRGB(GetBackgroundColour()); + + // first, draw the background + AutoHBRUSH hbrushBackground(colBg); + FillRect(hdc, &rectBtn, hbrushBackground); + + // draw the border for the current state + bool selected = (state & ODS_SELECTED) != 0; + if ( !selected ) + { + wxTopLevelWindow * + tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow); + if ( tlw ) + { + selected = tlw->GetDefaultItem() == this; + } + } + + DrawButtonFrame(hdc, rectBtn, selected, pushed); + } + + // draw the focus rectangle if we need it + if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) ) { - selected = panel->GetDefaultItem() == this; + DrawFocusRect(hdc, &rectBtn); + +#if wxUSE_UXTHEME + if ( !wxUxThemeEngine::GetIfActive() ) +#endif // wxUSE_UXTHEME + { + if ( pushed ) + { + // the label is shifted by 1 pixel to create "pushed" effect + OffsetRect(&rectBtn, 1, 1); + } + } } } - bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0; - DrawButtonFrame(hdc, rectBtn, selected, pushed); - // draw the focus rect if needed - if ( state & ODS_FOCUS ) + // draw the image, if any + if ( m_imageData ) { - RECT rectFocus; - CopyRect(&rectFocus, &rectBtn); + wxBitmap bmp = m_imageData->GetBitmap(GetButtonState(this, state)); + if ( !bmp.IsOk() ) + bmp = m_imageData->GetBitmap(State_Normal); + + const wxSize sizeBmp = bmp.GetSize(); + const wxSize margin = m_imageData->GetBitmapMargins(); + const wxSize sizeBmpWithMargins(sizeBmp + 2*margin); + wxRect rectButton(wxRectFromRECT(rectBtn)); + + // for simplicity, we start with centred rectangle and then move it to + // the appropriate edge + wxRect rectBitmap = wxRect(sizeBmp).CentreIn(rectButton); + switch ( m_imageData->GetBitmapPosition() ) + { + default: + wxFAIL_MSG( "invalid direction" ); + // fall through + + case wxLEFT: + rectBitmap.x = rectButton.x + margin.x; + rectButton.x += sizeBmpWithMargins.x; + rectButton.width -= sizeBmpWithMargins.x; + break; + + case wxRIGHT: + rectBitmap.x = rectButton.GetRight() - sizeBmp.x - margin.x; + rectButton.width -= sizeBmpWithMargins.x; + break; + + case wxTOP: + rectBitmap.y = rectButton.y + margin.y; + rectButton.y += sizeBmpWithMargins.y; + rectButton.height -= sizeBmpWithMargins.y; + break; + + case wxBOTTOM: + rectBitmap.y = rectButton.GetBottom() - sizeBmp.y - margin.y; + rectButton.height -= sizeBmpWithMargins.y; + break; + } - // I don't know where does this constant come from, but this is how - // Windows draws them - InflateRect(&rectFocus, -4, -4); + wxDCTemp dst((WXHDC)hdc); + dst.DrawBitmap(bmp, rectBitmap.GetPosition(), true); - DrawFocusRect(hdc, &rectFocus); + wxCopyRectToRECT(rectButton, rectBtn); } - if ( pushed ) + + // finally draw the label + if ( ShowsLabel() ) { - // the label is shifted by 1 pixel to create "pushed" effect - OffsetRect(&rectBtn, 1, 1); + COLORREF colFg = state & ODS_DISABLED + ? ::GetSysColor(COLOR_GRAYTEXT) + : wxColourToRGB(GetForegroundColour()); + + // notice that DT_HIDEPREFIX doesn't work on old (pre-Windows 2000) + // systems but by happy coincidence ODS_NOACCEL is not used under them + // neither so DT_HIDEPREFIX should never be used there + DrawButtonText(hdc, &rectBtn, GetLabel(), colFg, + state & ODS_NOACCEL ? DT_HIDEPREFIX : 0); } - DrawButtonText(hdc, &rectBtn, GetLabel(), - state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT) - : colFg); - - ::DeleteObject(hbrushBackground); - return true; } -#endif // __WIN32__ - #endif // wxUSE_BUTTON