X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/5f7bcb48fd9a642036a3bf5c70e2b0c9576df1b1..1c3e52af4b9f940848edd92caf2ddffc3d6aff64:/src/msw/button.cpp diff --git a/src/msw/button.cpp b/src/msw/button.cpp index 84bcc4d748..893dd88301 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,18 +26,51 @@ #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/stockitem.h" #include "wx/msw/private.h" +#include "wx/msw/private/button.h" + +#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 +#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 // ---------------------------------------------------------------------------- // macros @@ -108,14 +137,61 @@ 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(_T('\n')) != wxString::npos ) + styleNew = styleOld | BS_MULTILINE; + else + styleNew = styleOld & ~BS_MULTILINE; + + if ( styleNew != styleOld ) + ::SetWindowLong(hwnd, GWL_STYLE, styleNew); +} + +wxSize wxMSWButton::ComputeBestSize(wxControl *btn) +{ + wxClientDC dc(btn); + + wxCoord wBtn, + hBtn; + dc.GetMultiLineTextExtent(btn->GetLabelText(), &wBtn, &hBtn); + + // FIXME: this is pure guesswork, need to retrieve the real button margins + wBtn += 3*btn->GetCharWidth(); + hBtn = 11*EDIT_HEIGHT_FROM_CHAR_HEIGHT(hBtn)/10; + + // 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 sz = wxButton::GetDefaultSize(); + if ( wBtn < sz.x ) + wBtn = sz.x; + if ( hBtn < sz.y ) + hBtn = sz.y; + } + + wxSize best(wBtn, hBtn); + btn->CacheBestSize(best); + return best; +} + // ---------------------------------------------------------------------------- // creation/destruction // ---------------------------------------------------------------------------- @@ -131,33 +207,41 @@ bool wxButton::Create(wxWindow *parent, { wxString label(lbl); if (label.empty() && wxIsStockID(id)) - label = wxGetStockLabel(id); - + { + // 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); } wxButton::~wxButton() { + wxTopLevelWindow *tlw = wxDynamicCast(wxGetTopLevelParent(this), wxTopLevelWindow); + if ( tlw && tlw->GetTmpDefaultItem() == this ) + { + UnsetTmpDefault(); + } } // ---------------------------------------------------------------------------- @@ -177,7 +261,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 ) @@ -193,44 +276,24 @@ 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; - GetTextExtent(wxGetWindowText(GetHWND()), &wBtn, NULL); - - int wChar, hChar; - wxGetCharSize(GetHWND(), &wChar, &hChar, GetFont()); - - // add a margin -- the button is wider than just its label - wBtn += 3*wChar; - - // the button height is proportional to the height of the font used - int hBtn = BUTTON_HEIGHT_FROM_CHAR_HEIGHT(hChar); - - // 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; - - return sz; - } - - return wxSize(wBtn, hBtn); + return wxMSWButton::ComputeBestSize(wx_const_cast(wxButton *, this)); } /* static */ @@ -297,29 +360,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, _T("button without top level parent?") ); + + wxTopLevelWindow * const tlw = wxDynamicCast(win, wxTopLevelWindow); + wxASSERT_MSG( tlw, _T("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); @@ -328,13 +417,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); @@ -357,18 +446,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, _T("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 @@ -463,6 +547,27 @@ 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(); + } + else if ( wxUxThemeEngine::GetIfActive() ) + { + // 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 ) + { + Refresh(); + } + } +#endif // wxUSE_UXTHEME // let the base class do all real processing return wxControl::MSWWindowProc(nMsg, wParam, lParam); @@ -472,8 +577,6 @@ WXLRESULT wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam) // owner-drawn buttons support // ---------------------------------------------------------------------------- -#ifdef __WIN32__ - // drawing helpers static void DrawButtonText(HDC hdc, @@ -484,8 +587,32 @@ static void DrawButtonText(HDC hdc, COLORREF colOld = SetTextColor(hdc, col); int modeOld = SetBkMode(hdc, TRANSPARENT); - // Note: we must have DT_SINGLELINE for DT_VCENTER to work. - ::DrawText(hdc, text, text.length(), pRect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); + if ( text.find(_T('\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, DT_CENTER); + } + else // single line label + { + // Note: we must have DT_SINGLELINE for DT_VCENTER to work. + ::DrawText(hdc, text.wx_str(), text.length(), pRect, + DT_SINGLELINE | DT_CENTER | DT_VCENTER); + } SetBkMode(hdc, modeOld); SetTextColor(hdc, colOld); @@ -633,67 +760,144 @@ static void DrawButtonFrame(HDC hdc, const RECT& rectBtn, DeleteObject(hpenBlack); } -bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis) +#if wxUSE_UXTHEME +static +void MSWDrawXPBackground(wxButton *button, WXDRAWITEMSTRUCT *wxdis) { LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis; - - RECT rectBtn; - CopyRect(&rectBtn, &lpDIS->rcItem); - - COLORREF colBg = wxColourToRGB(GetBackgroundColour()), - colFg = wxColourToRGB(GetForegroundColour()); - HDC hdc = lpDIS->hDC; UINT state = lpDIS->itemState; + RECT rectBtn; + CopyRect(&rectBtn, &lpDIS->rcItem); - // first, draw the background - HBRUSH hbrushBackground = ::CreateSolidBrush(colBg); + wxUxThemeHandle theme(button, L"BUTTON"); + int iState; - FillRect(hdc, &rectBtn, hbrushBackground); + 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 + { + iState = PBS_NORMAL; + } - // draw the border for the current state - bool selected = (state & ODS_SELECTED) != 0; - if ( !selected ) + // draw parent background if needed + if ( wxUxThemeEngine::Get()->IsThemeBackgroundPartiallyTransparent(theme, + BP_PUSHBUTTON, + iState) ) { - wxPanel *panel = wxDynamicCast(GetParent(), wxPanel); - if ( panel ) - { - selected = panel->GetDefaultItem() == this; - } + wxUxThemeEngine::Get()->DrawThemeParentBackground(GetHwndOf(button), hdc, &rectBtn); } - bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0; - DrawButtonFrame(hdc, rectBtn, selected, pushed); + // draw background + wxUxThemeEngine::Get()->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); - // draw the focus rect if needed - if ( state & ODS_FOCUS ) + // if focused and !nofocus rect + if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) ) { - RECT rectFocus; - CopyRect(&rectFocus, &rectBtn); + DrawFocusRect(hdc, &rectClient); + } - // I don't know where does this constant come from, but this is how - // Windows draws them - InflateRect(&rectFocus, -4, -4); + if ( button->UseBgCol() ) + { + COLORREF colBg = wxColourToRGB(button->GetBackgroundColour()); + HBRUSH hbrushBackground = ::CreateSolidBrush(colBg); - DrawFocusRect(hdc, &rectFocus); + // don't overwrite the focus rect + ::InflateRect(&rectClient, -1, -1); + FillRect(hdc, &rectClient, hbrushBackground); + ::DeleteObject(hbrushBackground); } +} +#endif // wxUSE_UXTHEME - if ( pushed ) +bool wxButton::MSWOnDraw(WXDRAWITEMSTRUCT *wxdis) +{ + LPDRAWITEMSTRUCT lpDIS = (LPDRAWITEMSTRUCT)wxdis; + HDC hdc = lpDIS->hDC; + UINT state = lpDIS->itemState; + RECT rectBtn; + CopyRect(&rectBtn, &lpDIS->rcItem); + +#if wxUSE_UXTHEME + if ( wxUxThemeEngine::GetIfActive() ) { - // the label is shifted by 1 pixel to create "pushed" effect - OffsetRect(&rectBtn, 1, 1); + MSWDrawXPBackground(this, wxdis); } + else +#endif // wxUSE_UXTHEME + { + COLORREF colBg = wxColourToRGB(GetBackgroundColour()); - DrawButtonText(hdc, &rectBtn, GetLabel(), - state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT) - : colFg); + // first, draw the background + HBRUSH hbrushBackground = ::CreateSolidBrush(colBg); + FillRect(hdc, &rectBtn, hbrushBackground); + ::DeleteObject(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; + } + } + bool pushed = (SendMessage(GetHwnd(), BM_GETSTATE, 0, 0) & BST_PUSHED) != 0; + + DrawButtonFrame(hdc, rectBtn, selected, pushed); + + // if focused and !nofocus rect + if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) ) + { + RECT rectFocus; + CopyRect(&rectFocus, &rectBtn); - ::DeleteObject(hbrushBackground); + // I don't know where does this constant come from, but this is how + // Windows draws them + InflateRect(&rectFocus, -4, -4); + + DrawFocusRect(hdc, &rectFocus); + } + + if ( pushed ) + { + // the label is shifted by 1 pixel to create "pushed" effect + OffsetRect(&rectBtn, 1, 1); + } + } + + 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); return true; } -#endif // __WIN32__ - #endif // wxUSE_BUTTON