X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/05c5f281440ccd7f76f2b91a7d337667206b9ef4..81533a3af6ed598c32a35e1c1c2b60f4908f5541:/src/msw/button.cpp diff --git a/src/msw/button.cpp b/src/msw/button.cpp index 1c99e39ef9..407981a03a 100644 --- a/src/msw/button.cpp +++ b/src/msw/button.cpp @@ -39,9 +39,14 @@ #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" +#include "wx/private/window.h" + +using namespace wxMSWImpl; #if wxUSE_UXTHEME #include "wx/msw/uxtheme.h" @@ -58,6 +63,28 @@ #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 @@ -72,6 +99,255 @@ #define ODS_NOFOCUSRECT 0x0200 #endif +#ifndef DT_HIDEPREFIX + #define DT_HIDEPREFIX 0x00100000 +#endif + +// set the value for BCM_SETSHIELD (for the UAC shield) if it's not defined in +// the header +#ifndef BCM_SETSHIELD + #define BCM_SETSHIELD 0x160c +#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 // ---------------------------------------------------------------------------- @@ -137,14 +413,78 @@ 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, + int flags) +{ + // 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; + + // account for the shield UAC icon if we have it + if ( flags & Size_AuthNeeded ) + sizeBtn.x += wxSystemSettings::GetMetric(wxSYS_SMALLICON_X); + + return sizeBtn; +} + +wxSize wxMSWButton::ComputeBestSize(wxControl *btn, int flags) +{ + wxClientDC dc(btn); + + wxSize sizeBtn; + dc.GetMultiLineTextExtent(btn->GetLabelText(), &sizeBtn.x, &sizeBtn.y); + + sizeBtn = GetFittingSize(btn, sizeBtn, flags); + + // 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) ) + { + // The size of a standard button in the dialog units is 50x14, use it. + // Note that we intentionally don't use GetDefaultSize() here, because + // it's inexact -- dialog units depend on this dialog's font. + wxSize sizeDef = btn->ConvertDialogToPixels(wxSize(50, 14)); + 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 // ---------------------------------------------------------------------------- @@ -158,19 +498,19 @@ bool wxButton::Create(wxWindow *parent, const wxValidator& validator, const wxString& name) { + m_authNeeded = false; + 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) ) @@ -179,21 +519,15 @@ bool wxButton::Create(wxWindow *parent, 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() @@ -203,6 +537,8 @@ wxButton::~wxButton() { UnsetTmpDefault(); } + + delete m_imageData; } // ---------------------------------------------------------------------------- @@ -222,7 +558,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 ) @@ -238,45 +573,102 @@ 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 { - wxClientDC dc(wx_const_cast(wxButton *, this)); - dc.SetFont(GetFont()); + wxSize size; - wxCoord wBtn, - hBtn; - dc.GetMultiLineTextExtent(GetLabelText(), &wBtn, &hBtn); + // 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 ) + { + int flags = 0; + if ( GetAuthNeeded() ) + flags |= wxMSWButton::Size_AuthNeeded; - // add a margin -- the button is wider than just its label - wBtn += 3*GetCharWidth(); - hBtn = BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hBtn); + size = wxMSWButton::ComputeBestSize(const_cast(this), flags); + } - // 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) ) + if ( m_imageData ) { - wxSize sz = GetDefaultSize(); - if (wBtn > sz.x) - sz.x = wBtn; - if (hBtn > sz.y) - sz.y = hBtn; + // 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 ) + { + 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; + } + + // account for the user-specified margins + size += 2*m_imageData->GetBitmapMargins(); - return sz; + // 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); + } + + CacheBestSize(size); } - wxSize best(wBtn, hBtn); - CacheBestSize(best); - return best; + return size; } /* static */ @@ -289,16 +681,20 @@ wxSize wxButtonBase::GetDefaultSize() wxScreenDC dc; dc.SetFont(wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT)); - // the size of a standard button in the dialog units is 50x14, - // translate this to pixels - // NB1: the multipliers come from the Windows convention - // NB2: the extra +1/+2 were needed to get the size be the same as the - // size of the buttons in the standard dialog - I don't know how - // this happens, but on my system this size is 75x23 in pixels and - // 23*8 isn't even divisible by 14... Would be nice to understand - // why these constants are needed though! - s_sizeBtn.x = (50 * (dc.GetCharWidth() + 1))/4; - s_sizeBtn.y = ((14 * dc.GetCharHeight()) + 2)/8; + // The size of a standard button in the dialog units is 50x14, + // translate this to pixels. + // + // Windows' computes dialog units using average character width over + // upper- and lower-case ASCII alphabet and not using the average + // character width metadata stored in the font; see + // http://support.microsoft.com/default.aspx/kb/145994 for detailed + // discussion. + // + // NB: wxMulDivInt32() is used, because it correctly rounds the result + + const wxSize base = wxPrivate::GetAverageASCIILetterSize(dc); + s_sizeBtn.x = wxMulDivInt32(50, base.x, 4); + s_sizeBtn.y = wxMulDivInt32(14, base.y, 8); } return s_sizeBtn; @@ -309,6 +705,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?" @@ -355,25 +759,32 @@ wxWindow *wxButton::SetDefault() return winOldDefault; } -// special version of wxGetTopLevelParent() which is safe to call when the -// parent is being destroyed: wxGetTopLevelParent() would just return NULL in -// this case because wxWindow version of IsTopLevel() is used when it's called -// during window destruction instead of wxTLW one, but we want to distinguish -// between these cases +// return the top level parent window if it's not being deleted yet, otherwise +// return NULL static wxTopLevelWindow *GetTLWParentIfNotBeingDeleted(wxWindow *win) { - for ( ; win; win = win->GetParent() ) + for ( ;; ) { - if ( win->IsBeingDeleted() ) - return NULL; + // 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; - if ( win->IsTopLevel() ) break; + } + + win = parent; } - wxASSERT_MSG( win, _T("button without top level parent?") ); + wxASSERT_MSG( win, wxT("button without top level parent?") ); - return wxDynamicCast(win, wxTopLevelWindow); + wxTopLevelWindow * const tlw = wxDynamicCast(win, wxTopLevelWindow); + wxASSERT_MSG( tlw, wxT("logic error in GetTLWParentIfNotBeingDeleted()") ); + + return tlw; } // set this button as being currently default @@ -423,7 +834,7 @@ wxButton::SetDefaultStyle(wxButton *btn, bool on) return; wxWindow * const tlw = wxGetTopLevelParent(btn); - wxCHECK_RET( tlw, _T("button without top level window?") ); + wxCHECK_RET( tlw, wxT("button without top level window?") ); ::SendMessage(GetHwndOf(tlw), DM_SETDEFID, btn->GetId(), 0L); @@ -530,49 +941,164 @@ WXLRESULT wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) // as the theme size might have changed InvalidateBestSize(); } - else if ( wxUxThemeEngine::GetIfActive() ) +#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 ) { - // we need to Refresh() if mouse has entered or left window - // so we can update the hot tracking state - // 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 - 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(); } } -#endif // wxUSE_UXTHEME // let the base class do all real processing return wxControl::MSWWindowProc(nMsg, wParam, lParam); } // ---------------------------------------------------------------------------- -// owner-drawn buttons support +// authentication needed handling +// ---------------------------------------------------------------------------- + +bool wxButton::DoGetAuthNeeded() const +{ + return m_authNeeded; +} + +void wxButton::DoSetAuthNeeded(bool show) +{ + // show/hide UAC symbol on Windows Vista and later + if ( wxGetWinVersion() >= wxWinVersion_6 ) + { + m_authNeeded = show; + ::SendMessage(GetHwnd(), BCM_SETSHIELD, 0, show); + InvalidateBestSize(); + } +} + // ---------------------------------------------------------------------------- +// 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 + // 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); + } + + // 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(); + + Refresh(); +} + +wxSize wxButton::DoGetBitmapMargins() const +{ + return m_imageData ? m_imageData->GetBitmapMargins() : wxSize(0, 0); +} + +void wxButton::DoSetBitmapMargins(wxCoord x, wxCoord y) +{ + wxCHECK_RET( m_imageData, "SetBitmap() must be called first" ); + + m_imageData->SetBitmapMargins(x, y); + InvalidateBestSize(); +} + +void wxButton::DoSetBitmapPosition(wxDirection dir) +{ + wxCHECK_RET( m_imageData, "SetBitmap() must be called first" ); + + m_imageData->SetBitmapPosition(dir); + InvalidateBestSize(); +} -#ifdef __WIN32__ +// ---------------------------------------------------------------------------- +// owner-drawn buttons support +// ---------------------------------------------------------------------------- // drawing helpers +namespace +{ -static void DrawButtonText(HDC hdc, - RECT *pRect, - const wxString& text, - COLORREF col) +// 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) { - COLORREF colOld = SetTextColor(hdc, col); - int modeOld = SetBkMode(hdc, TRANSPARENT); + if ( state & ODS_DISABLED ) + return wxButton::State_Disabled; + + if ( state & ODS_SELECTED ) + return wxButton::State_Pressed; - if ( text.find(_T('\n')) != wxString::npos ) + if ( btn->HasCapture() || btn->IsMouseInWindow() ) + return wxButton::State_Current; + + if ( state & ODS_FOCUS ) + return wxButton::State_Focused; + + return wxButton::State_Normal; +} + +void DrawButtonText(HDC hdc, + RECT *pRect, + const wxString& text, + COLORREF col, + int flags) +{ + wxTextColoursChanger changeFg(hdc, col, CLR_INVALID); + wxBkModeChanger changeBkMode(hdc, wxBRUSHSTYLE_TRANSPARENT); + + // center text horizontally in any case + flags |= DT_CENTER; + + 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, text.length(), &rc, DT_CENTER | DT_CALCRECT); + ::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; @@ -582,20 +1108,18 @@ static void DrawButtonText(HDC hdc, rc.top = (pRect->bottom - pRect->top)/2 - h/2; rc.bottom = rc.top+h; - ::DrawText(hdc, text, text.length(), &rc, DT_CENTER); + ::DrawText(hdc, text.wx_str(), text.length(), &rc, flags); } else // single line label { - // Note: we must have DT_SINGLELINE for DT_VCENTER to work. - ::DrawText(hdc, text, text.length(), pRect, - DT_SINGLELINE | DT_CENTER | DT_VCENTER); + // 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); } - - SetBkMode(hdc, modeOld); - SetTextColor(hdc, colOld); } -static void DrawRect(HDC hdc, const RECT& r) +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); @@ -603,47 +1127,6 @@ static void DrawRect(HDC hdc, const RECT& r) wxDrawLine(hdc, r.left, r.bottom, r.left, r.top); } -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; -} - /* The button frame looks like this normally: @@ -679,19 +1162,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--; @@ -730,152 +1212,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); } #if wxUSE_UXTHEME -static -void MSWDrawXPBackground(wxButton *button, WXDRAWITEMSTRUCT *wxdis) +void DrawXPBackground(wxButton *button, HDC hdc, RECT& rectBtn, UINT state) { - LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis; - HDC hdc = lpDIS->hDC; - UINT state = lpDIS->itemState; - RECT rectBtn; - CopyRect(&rectBtn, &lpDIS->rcItem); - wxUxThemeHandle theme(button, L"BUTTON"); - int iState; - if ( state & ODS_SELECTED ) - { - iState = PBS_PRESSED; - } - else if ( button->HasCapture() || button->IsMouseInWindow() ) - { - iState = PBS_HOT; - } - else if ( state & ODS_FOCUS ) - { - iState = PBS_DEFAULTED; - } - else if ( state & ODS_DISABLED ) - { - iState = PBS_DISABLED; - } - else + // this array is indexed by wxButton::State values and so must be kept in + // sync with it + static const int uxStates[] = { - iState = PBS_NORMAL; - } + 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 ( wxUxThemeEngine::Get()->IsThemeBackgroundPartiallyTransparent(theme, - BP_PUSHBUTTON, - iState) ) + if ( engine->IsThemeBackgroundPartiallyTransparent + ( + theme, + BP_PUSHBUTTON, + iState + ) ) { - wxUxThemeEngine::Get()->DrawThemeParentBackground(GetHwndOf(button), hdc, &rectBtn); + engine->DrawThemeParentBackground(GetHwndOf(button), hdc, &rectBtn); } // draw background - wxUxThemeEngine::Get()->DrawThemeBackground(theme, hdc, BP_PUSHBUTTON, iState, - &rectBtn, NULL); + engine->DrawThemeBackground(theme, hdc, BP_PUSHBUTTON, iState, + &rectBtn, NULL); // calculate content area margins MARGINS margins; - wxUxThemeEngine::Get()->GetThemeMargins(theme, hdc, BP_PUSHBUTTON, iState, - TMT_CONTENTMARGINS, &rectBtn, &margins); - RECT rectClient; - ::CopyRect(&rectClient, &rectBtn); - ::InflateRect(&rectClient, -margins.cxLeftWidth, -margins.cyTopHeight); - - // if focused and !nofocus rect - if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) ) - { - DrawFocusRect(hdc, &rectClient); - } + 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()); - HBRUSH hbrushBackground = ::CreateSolidBrush(colBg); + AutoHBRUSH hbrushBackground(colBg); // don't overwrite the focus rect + RECT rectClient; + ::CopyRect(&rectClient, &rectBtn); ::InflateRect(&rectClient, -1, -1); FillRect(hdc, &rectClient, hbrushBackground); - ::DeleteObject(hbrushBackground); } } #endif // wxUSE_UXTHEME +} // 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; + bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0; + RECT rectBtn; CopyRect(&rectBtn, &lpDIS->rcItem); -#if wxUSE_UXTHEME - if ( wxUxThemeEngine::GetIfActive() ) + // draw the button background + if ( !HasFlag(wxBORDER_NONE) ) { - MSWDrawXPBackground(this, wxdis); - } - else +#if wxUSE_UXTHEME + if ( wxUxThemeEngine::GetIfActive() ) + { + DrawXPBackground(this, hdc, rectBtn, state); + } + else #endif // wxUSE_UXTHEME - { - COLORREF colBg = wxColourToRGB(GetBackgroundColour()); + { + COLORREF colBg = wxColourToRGB(GetBackgroundColour()); - // first, draw the background - HBRUSH hbrushBackground = ::CreateSolidBrush(colBg); - FillRect(hdc, &rectBtn, hbrushBackground); - ::DeleteObject(hbrushBackground); + // 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 ) + // draw the border for the current state + bool selected = (state & ODS_SELECTED) != 0; + if ( !selected ) { - selected = tlw->GetDefaultItem() == this; + wxTopLevelWindow * + tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow); + if ( tlw ) + { + selected = tlw->GetDefaultItem() == this; + } } - } - bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0; - DrawButtonFrame(hdc, rectBtn, selected, pushed); + DrawButtonFrame(hdc, rectBtn, selected, pushed); + } - // if focused and !nofocus rect + // draw the focus rectangle if we need it if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) ) { - RECT rectFocus; - CopyRect(&rectFocus, &rectBtn); - - // I don't know where does this constant come from, but this is how - // Windows draws them - InflateRect(&rectFocus, -4, -4); + DrawFocusRect(hdc, &rectBtn); - DrawFocusRect(hdc, &rectFocus); +#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); + } + } } + } + - if ( pushed ) + // draw the image, if any + if ( m_imageData ) + { + 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() ) { - // the label is shifted by 1 pixel to create "pushed" effect - OffsetRect(&rectBtn, 1, 1); + 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; } + + wxDCTemp dst((WXHDC)hdc); + dst.DrawBitmap(bmp, rectBitmap.GetPosition(), true); + + wxCopyRectToRECT(rectButton, rectBtn); } - COLORREF colFg = wxColourToRGB(GetForegroundColour()); - if ( state & ODS_DISABLED ) colFg = GetSysColor(COLOR_GRAYTEXT) ; - wxString label = GetLabel(); - if ( state & ODS_NOACCEL ) label = GetLabelText() ; - DrawButtonText(hdc, &rectBtn, label, colFg); + + // finally draw the label + if ( ShowsLabel() ) + { + 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); + } return true; } -#endif // __WIN32__ - #endif // wxUSE_BUTTON +