/////////////////////////////////////////////////////////////////////////////
-// Name: msw/button.cpp
+// Name: src/msw/button.cpp
// Purpose: wxButton
// Author: Julian Smart
// Modified by:
// 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"
#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"
+ #include "wx/imaglist.h"
#endif
+#include "wx/stockitem.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)
+ {
+ m_dir = wxLEFT;
+
+ 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 + wxSize(OD_BUTTON_MARGIN, OD_BUTTON_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
+
+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 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
// ----------------------------------------------------------------------------
#if wxUSE_EXTENDED_RTTI
+
+WX_DEFINE_FLAGS( wxButtonStyle )
+
+wxBEGIN_FLAGS( wxButtonStyle )
+ // new style border flags, we put them first to
+ // use them for streaming out
+ wxFLAGS_MEMBER(wxBORDER_SIMPLE)
+ wxFLAGS_MEMBER(wxBORDER_SUNKEN)
+ wxFLAGS_MEMBER(wxBORDER_DOUBLE)
+ wxFLAGS_MEMBER(wxBORDER_RAISED)
+ wxFLAGS_MEMBER(wxBORDER_STATIC)
+ wxFLAGS_MEMBER(wxBORDER_NONE)
+
+ // old style border flags
+ wxFLAGS_MEMBER(wxSIMPLE_BORDER)
+ wxFLAGS_MEMBER(wxSUNKEN_BORDER)
+ wxFLAGS_MEMBER(wxDOUBLE_BORDER)
+ wxFLAGS_MEMBER(wxRAISED_BORDER)
+ wxFLAGS_MEMBER(wxSTATIC_BORDER)
+ wxFLAGS_MEMBER(wxBORDER)
+
+ // standard window styles
+ wxFLAGS_MEMBER(wxTAB_TRAVERSAL)
+ wxFLAGS_MEMBER(wxCLIP_CHILDREN)
+ wxFLAGS_MEMBER(wxTRANSPARENT_WINDOW)
+ wxFLAGS_MEMBER(wxWANTS_CHARS)
+ wxFLAGS_MEMBER(wxFULL_REPAINT_ON_RESIZE)
+ wxFLAGS_MEMBER(wxALWAYS_SHOW_SB )
+ wxFLAGS_MEMBER(wxVSCROLL)
+ wxFLAGS_MEMBER(wxHSCROLL)
+
+ wxFLAGS_MEMBER(wxBU_LEFT)
+ wxFLAGS_MEMBER(wxBU_RIGHT)
+ wxFLAGS_MEMBER(wxBU_TOP)
+ wxFLAGS_MEMBER(wxBU_BOTTOM)
+ wxFLAGS_MEMBER(wxBU_EXACTFIT)
+wxEND_FLAGS( wxButtonStyle )
+
IMPLEMENT_DYNAMIC_CLASS_XTI(wxButton, wxControl,"wx/button.h")
-WX_BEGIN_PROPERTIES_TABLE(wxButton)
- WX_DELEGATE( OnClick , wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEvent )
+wxBEGIN_PROPERTIES_TABLE(wxButton)
+ wxEVENT_PROPERTY( Click , wxEVT_COMMAND_BUTTON_CLICKED , wxCommandEvent)
+
+ wxPROPERTY( Font , wxFont , SetFont , GetFont , EMPTY_MACROVALUE, 0 /*flags*/ , wxT("Helpstring") , wxT("group"))
+ wxPROPERTY( Label, wxString , SetLabel, GetLabel, wxString(), 0 /*flags*/ , wxT("Helpstring") , wxT("group") )
+
+ wxPROPERTY_FLAGS( WindowStyle , wxButtonStyle , long , SetWindowStyleFlag , GetWindowStyleFlag , EMPTY_MACROVALUE , 0 /*flags*/ , wxT("Helpstring") , wxT("group")) // style
- WX_PROPERTY( Font , wxFont , SetFont , GetFont , )
- WX_PROPERTY( Label,wxString, SetLabel, GetLabel, wxEmptyString )
-WX_END_PROPERTIES_TABLE()
+wxEND_PROPERTIES_TABLE()
-WX_BEGIN_HANDLERS_TABLE(wxButton)
-WX_END_HANDLERS_TABLE()
+wxBEGIN_HANDLERS_TABLE(wxButton)
+wxEND_HANDLERS_TABLE()
-WX_CONSTRUCTOR_6( wxButton , wxWindow* , Parent , wxWindowID , Id , wxString , Label , wxPoint , Position , wxSize , Size , long , WindowStyle )
+wxCONSTRUCTOR_6( wxButton , wxWindow* , Parent , wxWindowID , Id , wxString , Label , wxPoint , Position , wxSize , Size , long , WindowStyle )
#else
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::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
// ----------------------------------------------------------------------------
bool wxButton::Create(wxWindow *parent,
wxWindowID id,
- const wxString& label,
+ const wxString& lbl,
const wxPoint& pos,
const wxSize& size,
long style,
const wxValidator& validator,
const wxString& name)
{
+ wxString label(lbl);
+ if (label.empty() && wxIsStockID(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;
+ 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();
+ }
+
+ delete m_imageData;
}
// ----------------------------------------------------------------------------
// 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 )
msStyle |= BS_TOP;
if ( style & wxBU_BOTTOM )
msStyle |= BS_BOTTOM;
-#endif // __WIN32__
+#ifndef __WXWINCE__
+ // flat 2d buttons
+ if ( style & wxNO_BORDER )
+ msStyle |= BS_FLAT;
+#endif // __WXWINCE__
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 size = wxMSWButton::ComputeBestSize(const_cast<wxButton *>(this));
+ if ( m_imageData )
{
- wxSize sz = GetDefaultSize();
- if (wBtn > sz.x)
- sz.x = wBtn;
- if (hBtn > sz.y)
- sz.y = hBtn;
+ 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;
+ }
- return sz;
+ size += 2*m_imageData->GetBitmapMargins();
+
+ CacheBestSize(size);
}
- return wxSize(wBtn, hBtn);
+ return size;
}
/* static */
NB: all this is quite complicated by now and the worst is that normally
it shouldn't be necessary at all as for the normal Windows programs
DefWindowProc() and IsDialogMessage() take care of all this
- automatically -- however in wxWindows programs this doesn't work for
+ automatically -- however in wxWidgets programs this doesn't work for
nested hierarchies (i.e. a notebook inside a notebook) for unknown
reason and so we have to reproduce all this code ourselves. It would be
very nice if we could avoid doing it.
*/
// set this button as the (permanently) default one in its panel
-void wxButton::SetDefault()
+wxWindow *wxButton::SetDefault()
{
- wxWindow *parent = GetParent();
+ // set this one as the default button both for wxWidgets ...
+ wxWindow *winOldDefault = wxButtonBase::SetDefault();
- wxCHECK_RET( parent, _T("button without parent?") );
+ // ... and Windows
+ SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), false);
+ SetDefaultStyle(this, true);
- // set this one as the default button both for wxWindows ...
- wxWindow *winOldDefault = parent->SetDefaultItem(this);
+ return winOldDefault;
+}
- // ... and Windows
- SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), FALSE);
- SetDefaultStyle(this, TRUE);
+// 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);
+ SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), false);
+ SetDefaultStyle(this, true);
}
// 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);
+ SetDefaultStyle(this, false);
+ SetDefaultStyle(wxDynamicCast(winOldDefault, wxButton), true);
}
/* static */
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
bool wxButton::MSWCommand(WXUINT param, WXWORD WXUNUSED(id))
{
- bool processed = FALSE;
+ bool processed = false;
switch ( param )
{
// NOTE: Apparently older versions (NT 4?) of the common controls send
// BN_DOUBLECLICKED but not a second BN_CLICKED for owner-drawn
- // buttons, so in order to send two EVET_BUTTON events we should
+ // buttons, so in order to send two EVT_BUTTON events we should
// catch both types. Currently (Feb 2003) up-to-date versions of
// win98, win2k and winXP all send two BN_CLICKED messages for
// all button types, so we don't catch BN_DOUBLECLICKED anymore
return processed;
}
-long wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
+WXLRESULT wxButton::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam)
{
- // when we receive focus, we want to temporary become the default button in
+ // when we receive focus, we want to temporarily become the default button in
// our parent panel so that pressing "Enter" would activate us -- and when
// losing it we should restore the previous default button as well
if ( nMsg == WM_SETFOCUS )
// 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);
}
// ----------------------------------------------------------------------------
-// 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
+ if ( wxUxThemeEngine::GetIfActive() )
+ {
+ m_imageData = new wxXPButtonImageData(this, bitmap.GetSize());
+ }
+ else
+#endif // wxUSE_UXTHEME
+ {
+ m_imageData = new wxODButtonImageData(this);
+ MakeOwnerDrawn();
+ }
- // Note: we must have DT_SINGLELINE for DT_VCENTER to work.
- ::DrawText(hdc, text, text.length(), pRect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
+ // if a bitmap was assigned to the bitmap, its best size must be
+ // changed to account for it
+ InvalidateBestSize();
+ }
- SetBkMode(hdc, modeOld);
- SetTextColor(hdc, colOld);
+ m_imageData->SetBitmap(bitmap, which);
}
-static void DrawRect(HDC hdc, const RECT& r)
+void wxButton::DoSetBitmapMargins(wxCoord x, wxCoord y)
{
- 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);
-}
+ wxCHECK_RET( m_imageData, "SetBitmap() must be called first" );
-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);
- }
+ 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
+{
-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(_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, 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);
}
/*
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--;
DrawRect(hdc, r);
(void)SelectObject(hdc, hpenGrey);
- InflateRect(&r, -1, -1);
+ ::InflateRect(&r, -1, -1);
DrawRect(hdc, r);
}
{
DrawRect(hdc, r);
- InflateRect(&r, -1, -1);
+ ::InflateRect(&r, -1, -1);
}
wxDrawLine(hdc, r.left, r.bottom, r.right, r.bottom);
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
+void MSWDrawXPBackground(wxButton *button, HDC hdc, RECT& rectBtn, UINT state)
+{
+ 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
+ {
+ iState = PBS_NORMAL;
+ }
+
+ // draw parent background if needed
+ if ( wxUxThemeEngine::Get()->IsThemeBackgroundPartiallyTransparent(theme,
+ BP_PUSHBUTTON,
+ iState) )
+ {
+ wxUxThemeEngine::Get()->DrawThemeParentBackground(GetHwndOf(button), hdc, &rectBtn);
+ }
+
+ // 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);
+ ::InflateRect(&rectBtn, -margins.cxLeftWidth, -margins.cyTopHeight);
+
+ 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
+
+} // 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);
- COLORREF colBg = wxColourToRGB(GetBackgroundColour()),
- colFg = wxColourToRGB(GetForegroundColour());
+ // draw the button background
+#if wxUSE_UXTHEME
+ if ( wxUxThemeEngine::GetIfActive() )
+ {
+ MSWDrawXPBackground(this, hdc, rectBtn, state);
+ }
+ else
+#endif // wxUSE_UXTHEME
+ {
+ COLORREF colBg = wxColourToRGB(GetBackgroundColour());
- HDC hdc = lpDIS->hDC;
- UINT state = lpDIS->itemState;
+ // first, draw the background
+ AutoHBRUSH hbrushBackground(colBg);
+ FillRect(hdc, &rectBtn, hbrushBackground);
- // first, draw the background
- HBRUSH hbrushBackground = ::CreateSolidBrush(colBg);
+ // 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;
+ }
+ }
- FillRect(hdc, &rectBtn, hbrushBackground);
+ DrawButtonFrame(hdc, rectBtn, selected, pushed);
+ }
- // draw the border for the current state
- bool selected = (state & ODS_SELECTED) != 0;
- if ( !selected )
+ // draw the focus rectangle if we need it
+ if ( (state & ODS_FOCUS) && !(state & ODS_NOFOCUSRECT) )
{
- wxPanel *panel = wxDynamicCast(GetParent(), wxPanel);
- if ( panel )
+ DrawFocusRect(hdc, &rectBtn);
+
+#if wxUSE_UXTHEME
+ if ( !wxUxThemeEngine::GetIfActive() )
+#endif // wxUSE_UXTHEME
{
- selected = panel->GetDefaultItem() == this;
+ 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(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 )
- {
- // the label is shifted by 1 pixel to create "pushed" effect
- OffsetRect(&rectBtn, 1, 1);
- }
- DrawButtonText(hdc, &rectBtn, GetLabel(),
- state & ODS_DISABLED ? GetSysColor(COLOR_GRAYTEXT)
- : colFg);
+ // finally draw the label
+ COLORREF colFg = state & ODS_DISABLED
+ ? ::GetSysColor(COLOR_GRAYTEXT)
+ : wxColourToRGB(GetForegroundColour());
- ::DeleteObject(hbrushBackground);
+ // 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;
+ return true;
}
-#endif // __WIN32__
-
#endif // wxUSE_BUTTON