X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/1be45608e7f6d31f89b12139a42361f555ad68fc..43c42c18d36c703a88b1b7b697bac27fe5608eca:/src/msw/toolbar.cpp diff --git a/src/msw/toolbar.cpp b/src/msw/toolbar.cpp index 7e0a93f4ed..1011c5d3e2 100644 --- a/src/msw/toolbar.cpp +++ b/src/msw/toolbar.cpp @@ -36,6 +36,7 @@ #include "wx/intl.h" #include "wx/settings.h" #include "wx/bitmap.h" + #include "wx/region.h" #include "wx/dcmemory.h" #include "wx/control.h" #include "wx/app.h" // for GetComCtl32Version @@ -43,8 +44,10 @@ #include "wx/stattext.h" #endif +#include "wx/artprov.h" #include "wx/sysopt.h" #include "wx/dcclient.h" +#include "wx/scopedarray.h" #include "wx/msw/private.h" #include "wx/msw/dc.h" @@ -96,10 +99,6 @@ #define TB_GETMAXSIZE (WM_USER + 83) #endif -// these values correspond to those used by comctl32.dll -#define DEFAULTBITMAPX 16 -#define DEFAULTBITMAPY 15 - // ---------------------------------------------------------------------------- // wxWin macros // ---------------------------------------------------------------------------- @@ -151,7 +150,7 @@ public: clientData, shortHelp, longHelp) { m_nSepCount = 0; - m_staticText = 0; + m_staticText = NULL; } wxToolBarTool(wxToolBar *tbar, wxControl *control, const wxString& label) @@ -202,7 +201,7 @@ public: wxStaticText* GetStaticText() { wxASSERT_MSG( IsControl(), - _T("only makes sense for embedded control tools") ); + wxT("only makes sense for embedded control tools") ); return m_staticText; } @@ -212,13 +211,63 @@ public: void SetSeparatorsCount(size_t count) { m_nSepCount = count; } size_t GetSeparatorsCount() const { return m_nSepCount; } + // we need ids for the spacers which we want to modify later on, this + // function will allocate a valid/unique id for a spacer if not done yet + void AllocSpacerId() + { + if ( m_id == wxID_SEPARATOR ) + m_id = wxWindow::NewControlId(); + } + + // this method is used for controls only and offsets the control by the + // given amount (in pixels) in horizontal direction + void MoveBy(int offset) + { + wxControl * const control = GetControl(); + + control->Move(control->GetPosition().x + offset, wxDefaultCoord); + + if ( m_staticText ) + { + m_staticText->Move(m_staticText->GetPosition().x + offset, + wxDefaultCoord); + } + } + private: size_t m_nSepCount; wxStaticText *m_staticText; - DECLARE_NO_COPY_CLASS(wxToolBarTool) + wxDECLARE_NO_COPY_CLASS(wxToolBarTool); }; +// ---------------------------------------------------------------------------- +// helper functions +// ---------------------------------------------------------------------------- + +// return the rectangle of the item at the given index +// +// returns an empty (0, 0, 0, 0) rectangle if fails so the caller may compare +// r.right or r.bottom with 0 to check for this +static RECT wxGetTBItemRect(HWND hwnd, int index) +{ + RECT r; + + // note that we use TB_GETITEMRECT and not TB_GETRECT because the latter + // only appeared in v4.70 of comctl32.dll + if ( !::SendMessage(hwnd, TB_GETITEMRECT, index, (LPARAM)&r) ) + { + wxLogLastError(wxT("TB_GETITEMRECT")); + + r.top = + r.left = + r.right = + r.bottom = 0; + } + + return r; +} + // ============================================================================ // implementation // ============================================================================ @@ -256,9 +305,15 @@ void wxToolBar::Init() m_disabledImgList = NULL; m_nButtons = 0; + m_totalFixedSize = 0; - m_defaultWidth = DEFAULTBITMAPX; - m_defaultHeight = DEFAULTBITMAPY; + // even though modern Windows applications typically use 24*24 (or even + // 32*32) size for their bitmaps, the native control itself still uses the + // old 16*15 default size (see TB_SETBITMAPSIZE documentation in MSDN), so + // default to it so that we don't call SetToolBitmapSize() unnecessarily in + // wxToolBarBase::AdjustToolBitmapSize() + m_defaultWidth = 16; + m_defaultHeight = 15; m_pInTool = NULL; } @@ -282,17 +337,14 @@ bool wxToolBar::Create(wxWindow *parent, wxSetCCUnicodeFormat(GetHwnd()); - // workaround for flat toolbar on Windows XP classic style: we have to set - // the style after creating the control; doing it at creation time doesn't work -#if wxUSE_UXTHEME - if ( style & wxTB_FLAT ) + // we always erase our background on WM_PAINT so there is no need to do it + // in WM_ERASEBKGND too (by default this won't be done but if the toolbar + // has a non default background colour, then it would be used in both + // places resulting in flicker) + if (wxApp::GetComCtl32Version() >= 600) { - LRESULT style = GetMSWToolbarStyle(); - - if ( !(style & TBSTYLE_FLAT) ) - ::SendMessage(GetHwnd(), TB_SETSTYLE, 0, style | TBSTYLE_FLAT); + SetBackgroundStyle(wxBG_STYLE_PAINT); } -#endif // wxUSE_UXTHEME return true; } @@ -331,7 +383,7 @@ void wxToolBar::Recreate() if ( !MSWCreateToolbar(pos, size) ) { // what can we do? - wxFAIL_MSG( _T("recreating the toolbar failed") ); + wxFAIL_MSG( wxT("recreating the toolbar failed") ); return; } @@ -357,11 +409,7 @@ void wxToolBar::Recreate() m_hBitmap = 0; } - if ( m_disabledImgList ) - { - delete m_disabledImgList; - m_disabledImgList = NULL; - } + wxDELETE(m_disabledImgList); Realize(); } @@ -370,9 +418,7 @@ wxToolBar::~wxToolBar() { // we must refresh the frame size when the toolbar is deleted but the frame // is not - otherwise toolbar leaves a hole in the place it used to occupy - wxFrame *frame = wxDynamicCast(GetParent(), wxFrame); - if ( frame && !frame->IsBeingDeleted() ) - frame->SendSizeEvent(); + SendSizeEventToParent(); if ( m_hBitmap ) ::DeleteObject((HBITMAP) m_hBitmap); @@ -401,13 +447,27 @@ wxSize wxToolBar::DoGetBestSize() const sizeBest.y = t; } } - else + else // TB_GETMAXSIZE succeeded { + // but it could still return an incorrect result due to what appears to + // be a bug in old comctl32.dll versions which don't handle controls in + // the toolbar correctly, so work around it (see SF patch 1902358) + if ( !IsVertical() && wxApp::GetComCtl32Version() < 600 ) + { + // calculate the toolbar width in alternative way + const RECT rcFirst = wxGetTBItemRect(GetHwnd(), 0); + const RECT rcLast = wxGetTBItemRect(GetHwnd(), GetToolsCount() - 1); + + const int widthAlt = rcLast.right - rcFirst.left; + if ( widthAlt > size.cx ) + size.cx = widthAlt; + } + sizeBest.x = size.cx; sizeBest.y = size.cy; } - if (!IsVertical()) + if ( !IsVertical() ) { // Without the extra height, DoGetBestSize can report a size that's // smaller than the actual window, causing windows to overlap slightly @@ -435,21 +495,11 @@ WXDWORD wxToolBar::MSWGetStyle(long style, WXDWORD *exstyle) const if ( !(style & wxTB_NO_TOOLTIPS) ) msStyle |= TBSTYLE_TOOLTIPS; - if ( style & (wxTB_FLAT | wxTB_HORZ_LAYOUT) ) - { - // static as it doesn't change during the program lifetime - static const int s_verComCtl = wxApp::GetComCtl32Version(); - - // comctl32.dll 4.00 doesn't support the flat toolbars and using this - // style with 6.00 (part of Windows XP) leads to the toolbar with - // incorrect background colour - and not using it still results in the - // correct (flat) toolbar, so don't use it there - if ( s_verComCtl > 400 && s_verComCtl < 600 ) - msStyle |= TBSTYLE_FLAT | TBSTYLE_TRANSPARENT; - - if ( s_verComCtl >= 470 && style & wxTB_HORZ_LAYOUT ) - msStyle |= TBSTYLE_LIST; - } + if ( style & wxTB_FLAT && wxApp::GetComCtl32Version() > 400 ) + msStyle |= TBSTYLE_FLAT; + + if ( style & wxTB_HORZ_LAYOUT && wxApp::GetComCtl32Version() >= 470 ) + msStyle |= TBSTYLE_LIST; if ( style & wxTB_NODIVIDER ) msStyle |= CCS_NODIVIDER; @@ -466,6 +516,15 @@ WXDWORD wxToolBar::MSWGetStyle(long style, WXDWORD *exstyle) const if ( style & wxTB_RIGHT ) msStyle |= CCS_RIGHT; + // always use TBSTYLE_TRANSPARENT because the background is not drawn + // correctly without it in all themes and, for whatever reason, the control + // also flickers horribly when it is resized if this style is not used + // + // note that this is implicitly enabled by the native toolbar itself when + // TBSTYLE_FLAT is used (i.e. it's impossible to use TBSTYLE_FLAT without + // TBSTYLE_TRANSPARENT) but turn it on explicitly in any case + msStyle |= TBSTYLE_TRANSPARENT; + return msStyle; } @@ -473,7 +532,8 @@ WXDWORD wxToolBar::MSWGetStyle(long style, WXDWORD *exstyle) const // adding/removing tools // ---------------------------------------------------------------------------- -bool wxToolBar::DoInsertTool(size_t WXUNUSED(pos), wxToolBarToolBase *tool) +bool wxToolBar::DoInsertTool(size_t WXUNUSED(pos), + wxToolBarToolBase * WXUNUSED(tool)) { // nothing special to do here - we really create the toolbar buttons in // Realize() later @@ -510,20 +570,20 @@ bool wxToolBar::DoDeleteTool(size_t pos, wxToolBarToolBase *tool) size_t nButtonsToDelete = 1; // get the size of the button we're going to delete - RECT r; - if ( !::SendMessage(GetHwnd(), TB_GETITEMRECT, pos, (LPARAM)&r) ) - { - wxLogLastError(_T("TB_GETITEMRECT")); - } + const RECT r = wxGetTBItemRect(GetHwnd(), pos); - int width = r.right - r.left; + int delta = IsVertical() ? r.bottom - r.top : r.right - r.left; if ( tool->IsControl() ) { nButtonsToDelete = ((wxToolBarTool *)tool)->GetSeparatorsCount(); - width *= nButtonsToDelete; + + if ( !IsVertical() ) + delta *= nButtonsToDelete; } + m_totalFixedSize -= delta; + // do delete all buttons m_nButtons -= nButtonsToDelete; while ( nButtonsToDelete-- > 0 ) @@ -536,20 +596,45 @@ bool wxToolBar::DoDeleteTool(size_t pos, wxToolBarToolBase *tool) } } - // and finally reposition all the controls after this button (the toolbar - // takes care of all normal items) - for ( /* node -> first after deleted */ ; node; node = node->GetNext() ) + // and finally rearrange the tools + + // search for any stretch spacers before the removed tool + bool hasPrecedingStrechables = false; + for ( wxToolBarToolsList::compatibility_iterator nodeStch = m_tools.GetFirst(); + nodeStch != node; nodeStch = nodeStch->GetNext() ) { - wxToolBarTool *tool2 = (wxToolBarTool*)node->GetData(); - if ( tool2->IsControl() ) + if ( ((wxToolBarTool*)nodeStch->GetData())->IsStretchable() ) { - int x; - wxControl *control = tool2->GetControl(); - control->GetPosition(&x, NULL); - control->Move(x - width, wxDefaultCoord); + hasPrecedingStrechables = true; + break; + } + } + + if ( hasPrecedingStrechables ) + { + // if the removed tool is preceded by stretch spacers + // just redistribute the space + UpdateStretchableSpacersSize(); + } + else + { + // reposition all the controls after this button but before any + // stretch spacer (the toolbar takes care of all normal items) + for ( /* node -> first after deleted */ ; node; node = node->GetNext() ) + { + wxToolBarTool *tool2 = (wxToolBarTool*)node->GetData(); - wxStaticText* staticText = tool2->GetStaticText(); - staticText->Move(x - width, wxDefaultCoord); + if ( tool2->IsControl() ) + { + tool2->MoveBy(-delta); + } + + // if a stretch spacer is found just redistribute the available space + else if ( tool2->IsStretchable() ) + { + UpdateStretchableSpacersSize(); + break; + } } } @@ -560,11 +645,7 @@ bool wxToolBar::DoDeleteTool(size_t pos, wxToolBarToolBase *tool) void wxToolBar::CreateDisabledImageList() { - if (m_disabledImgList != NULL) - { - delete m_disabledImgList; - m_disabledImgList = NULL; - } + wxDELETE(m_disabledImgList); // as we can't use disabled image list with older versions of comctl32.dll, // don't even bother creating it @@ -576,12 +657,13 @@ void wxToolBar::CreateDisabledImageList() { wxToolBarToolBase *tool = node->GetData(); wxBitmap bmpDisabled = tool->GetDisabledBitmap(); - if ( bmpDisabled.Ok() ) + if ( bmpDisabled.IsOk() ) { + const wxSize sizeBitmap = bmpDisabled.GetSize(); m_disabledImgList = new wxImageList ( - m_defaultWidth, - m_defaultHeight, + sizeBitmap.x, + sizeBitmap.y, bmpDisabled.GetMask() != NULL, GetToolsCount() ); @@ -595,10 +677,10 @@ void wxToolBar::CreateDisabledImageList() bool wxToolBar::Realize() { + if ( !wxToolBarBase::Realize() ) + return false; + const size_t nTools = GetToolsCount(); - if ( nTools == 0 ) - // nothing to do - return true; #ifdef wxREMAP_BUTTON_COLOURS // don't change the values of these constants, they can be set from the @@ -637,22 +719,12 @@ bool wxToolBar::Realize() wxToolBarToolsList::compatibility_iterator node; int bitmapId = 0; - wxSize sizeBmp; - if ( HasFlag(wxTB_NOICONS) ) - { - // no icons, don't leave space for them - sizeBmp.x = - sizeBmp.y = 0; - } - else // do show icons + if ( !HasFlag(wxTB_NOICONS) ) { // if we already have a bitmap, we'll replace the existing one -- // otherwise we'll install a new one HBITMAP oldToolBarBitmap = (HBITMAP)m_hBitmap; - sizeBmp.x = m_defaultWidth; - sizeBmp.y = m_defaultHeight; - const wxCoord totalBitmapWidth = m_defaultWidth * wx_truncate_cast(wxCoord, nTools), totalBitmapHeight = m_defaultHeight; @@ -711,7 +783,7 @@ bool wxToolBar::Realize() const int w = bmp.GetWidth(); const int h = bmp.GetHeight(); - if ( bmp.Ok() ) + if ( bmp.IsOk() ) { int xOffset = wxMax(0, (m_defaultWidth - w)/2); int yOffset = wxMax(0, (m_defaultHeight - h)/2); @@ -721,7 +793,7 @@ bool wxToolBar::Realize() } else { - wxFAIL_MSG( _T("invalid tool button bitmap") ); + wxFAIL_MSG( wxT("invalid tool button bitmap") ); } // also deal with disabled bitmap if we want to use them @@ -729,7 +801,7 @@ bool wxToolBar::Realize() { wxBitmap bmpDisabled = tool->GetDisabledBitmap(); #if wxUSE_IMAGE && wxUSE_WXDIB - if ( !bmpDisabled.Ok() ) + if ( !bmpDisabled.IsOk() ) { // no disabled bitmap specified but we still need to // fill the space in the image list with something, so @@ -856,18 +928,11 @@ bool wxToolBar::Realize() } } - // don't call SetToolBitmapSize() as we don't want to change the values of - // m_defaultWidth/Height - if ( !::SendMessage(GetHwnd(), TB_SETBITMAPSIZE, 0, - MAKELONG(sizeBmp.x, sizeBmp.y)) ) - { - wxLogLastError(_T("TB_SETBITMAPSIZE")); - } // Next add the buttons and separators // ----------------------------------- - TBBUTTON *buttons = new TBBUTTON[nTools]; + wxScopedArray buttons(new TBBUTTON[nTools]); // this array will hold the indices of all controls in the toolbar wxArrayInt controlIds; @@ -876,7 +941,7 @@ bool wxToolBar::Realize() int i = 0; for ( node = m_tools.GetFirst(); node; node = node->GetNext() ) { - wxToolBarToolBase *tool = node->GetData(); + wxToolBarTool *tool = static_cast(node->GetData()); // don't add separators to the vertical toolbar with old comctl32.dll // versions as they didn't handle this properly @@ -894,10 +959,20 @@ bool wxToolBar::Realize() switch ( tool->GetStyle() ) { case wxTOOL_STYLE_CONTROL: - button.idCommand = tool->GetId(); - // fall through: create just a separator too - case wxTOOL_STYLE_SEPARATOR: + if ( tool->IsStretchableSpace() ) + { + // we're going to modify the size of this button later and + // so we need a valid id for it and not wxID_SEPARATOR + // which is used by spacers by default + tool->AllocSpacerId(); + + // also set the number of separators so that the logic in + // HandlePaint() works correctly + tool->SetSeparatorsCount(1); + } + + button.idCommand = tool->GetId(); button.fsState = TBSTATE_ENABLED; button.fsStyle = TBSTYLE_SEP; break; @@ -910,7 +985,7 @@ bool wxToolBar::Realize() { const wxString& label = tool->GetLabel(); if ( !label.empty() ) - button.iString = (INT_PTR)label.wx_str(); + button.iString = (INT_PTR) wxMSW_CONV_LPCTSTR(label); } button.idCommand = tool->GetId(); @@ -973,11 +1048,19 @@ bool wxToolBar::Realize() break; default: - wxFAIL_MSG( _T("unexpected toolbar button kind") ); + wxFAIL_MSG( wxT("unexpected toolbar button kind") ); button.fsStyle = TBSTYLE_BUTTON; break; } + // Instead of using fixed widths for all buttons, size them + // automatically according to the size of their bitmap and text + // label, if present. This particularly matters for toolbars + // with the wxTB_HORZ_LAYOUT style: they look hideously ugly + // without autosizing when the labels have even slightly + // different lengths. + button.fsStyle |= TBSTYLE_AUTOSIZE; + bitmapId++; break; } @@ -987,48 +1070,44 @@ bool wxToolBar::Realize() i++; } - if ( !::SendMessage(GetHwnd(), TB_ADDBUTTONS, (WPARAM)i, (LPARAM)buttons) ) + if ( !::SendMessage(GetHwnd(), TB_ADDBUTTONS, i, (LPARAM)buttons.get()) ) { wxLogLastError(wxT("TB_ADDBUTTONS")); } - delete [] buttons; - // Deal with the controls finally - // ------------------------------ + // Adjust controls and stretchable spaces + // -------------------------------------- - // adjust the controls size to fit nicely in the toolbar - int y = 0; - size_t index = 0; - for ( node = m_tools.GetFirst(); node; node = node->GetNext(), index++ ) + // adjust the controls size to fit nicely in the toolbar and compute its + // total size while doing it + m_totalFixedSize = 0; + int toolIndex = 0; + for ( node = m_tools.GetFirst(); node; node = node->GetNext(), toolIndex++ ) { - wxToolBarTool *tool = (wxToolBarTool*)node->GetData(); + wxToolBarTool * const tool = (wxToolBarTool*)node->GetData(); - // we calculate the running y coord for vertical toolbars so we need to - // get the items size for all items but for the horizontal ones we - // don't need to deal with the non controls - bool isControl = tool->IsControl(); - if ( !isControl && !IsVertical() ) - continue; + const RECT r = wxGetTBItemRect(GetHwnd(), toolIndex); - // note that we use TB_GETITEMRECT and not TB_GETRECT because the - // latter only appeared in v4.70 of comctl32.dll - RECT r; - if ( !::SendMessage(GetHwnd(), TB_GETITEMRECT, - index, (LPARAM)(LPRECT)&r) ) + if ( !tool->IsControl() ) { - wxLogLastError(wxT("TB_GETITEMRECT")); + if ( IsVertical() ) + m_totalFixedSize += r.bottom - r.top; + else + m_totalFixedSize += r.right - r.left; + + continue; } - if ( !isControl ) + if ( IsVertical() ) { - // can only be control if isVertical - y += r.bottom - r.top; - + // don't embed controls in the vertical toolbar, this doesn't look + // good and wxGTK doesn't do it neither (and the code below can't + // deal with this case) continue; } - wxControl *control = tool->GetControl(); + wxControl * const control = tool->GetControl(); wxStaticText * const staticText = tool->GetStaticText(); wxSize size = control->GetSize(); @@ -1039,9 +1118,6 @@ bool wxToolBar::Realize() staticTextSize.y += 3; // margin between control and its label } - // the position of the leftmost controls corner - int left = wxDefaultCoord; - // TB_SETBUTTONINFO message is only supported by comctl32.dll 4.71+ #ifdef TB_SETBUTTONINFO // available in headers, now check whether it is available now @@ -1067,7 +1143,6 @@ bool wxToolBar::Realize() { // try adding several separators to fit the controls width int widthSep = r.right - r.left; - left = r.left; TBBUTTON tbb; wxZeroMemory(tbb); @@ -1079,17 +1154,17 @@ bool wxToolBar::Realize() for ( size_t nSep = 0; nSep < nSeparators; nSep++ ) { if ( !::SendMessage(GetHwnd(), TB_INSERTBUTTON, - index, (LPARAM)&tbb) ) + toolIndex, (LPARAM)&tbb) ) { wxLogLastError(wxT("TB_INSERTBUTTON")); } - index++; + toolIndex++; } // remember the number of separators we used - we'd have to // delete all of them later - ((wxToolBarTool *)tool)->SetSeparatorsCount(nSeparators); + tool->SetSeparatorsCount(nSeparators); // adjust the controls width to exactly cover the separators size.x = (nSeparators + 1)*widthSep; @@ -1124,33 +1199,19 @@ bool wxToolBar::Realize() staticText->Show(); } - int top; - if ( IsVertical() ) - { - left = 0; - top = y; - - y += height + 2 * GetMargins().y; - } - else // horizontal toolbar - { - if ( left == wxDefaultCoord ) - left = r.left; - - top = r.top; - } - - control->Move(left, top + (diff + 1) / 2); + control->Move(r.left, r.top + (diff + 1) / 2); if ( staticText ) { - staticText->Move(left + (size.x - staticTextSize.x)/2, + staticText->Move(r.left + (size.x - staticTextSize.x)/2, r.bottom - staticTextSize.y); } + + m_totalFixedSize += size.x; } // the max index is the "real" number of buttons - i.e. counting even the // separators which we added just for aligning the controls - m_nButtons = index; + m_nButtons = toolIndex; if ( !IsVertical() ) { @@ -1171,6 +1232,82 @@ bool wxToolBar::Realize() return true; } +void wxToolBar::UpdateStretchableSpacersSize() +{ +#ifdef TB_SETBUTTONINFO + // we can't resize the spacers if TB_SETBUTTONINFO is not supported (we + // could try to do it with multiple separators as for the controls but this + // is too painful and it just doesn't seem to be worth doing for the + // ancient systems) + if ( wxApp::GetComCtl32Version() < 471 ) + return; + + // check if we have any stretchable spacers in the first place + unsigned numSpaces = 0; + wxToolBarToolsList::compatibility_iterator node; + for ( node = m_tools.GetFirst(); node; node = node->GetNext() ) + { + wxToolBarTool * const tool = (wxToolBarTool*)node->GetData(); + if ( tool->IsStretchableSpace() ) + numSpaces++; + } + + if ( !numSpaces ) + return; + + // we do, adjust their size: either distribute the extra size among them or + // reduce their size if there is not enough place for all tools + const int totalSize = IsVertical() ? GetClientSize().y : GetClientSize().x; + const int extraSize = totalSize - m_totalFixedSize; + const int sizeSpacer = extraSize > 0 ? extraSize / numSpaces : 1; + + // the last spacer should consume all remaining space if we have too much + // of it (which can be greater than sizeSpacer because of the rounding) + const int sizeLastSpacer = extraSize > 0 + ? extraSize - (numSpaces - 1)*sizeSpacer + : 1; + + // cumulated offset by which we need to move all the following controls to + // the right: while the toolbar takes care of the normal items, we must + // move the controls manually ourselves to ensure they remain at the + // correct place + int offset = 0; + int toolIndex = 0; + for ( node = m_tools.GetFirst(); node; node = node->GetNext(), toolIndex++ ) + { + wxToolBarTool * const tool = (wxToolBarTool*)node->GetData(); + + if ( tool->IsControl() && offset ) + { + tool->MoveBy(offset); + + continue; + } + + if ( !tool->IsStretchableSpace() ) + continue; + + const RECT rcOld = wxGetTBItemRect(GetHwnd(), toolIndex); + + WinStruct tbbi; + tbbi.dwMask = TBIF_SIZE; + tbbi.cx = --numSpaces ? sizeSpacer : sizeLastSpacer; + + if ( !::SendMessage(GetHwnd(), TB_SETBUTTONINFO, + tool->GetId(), (LPARAM)&tbbi) ) + { + wxLogLastError(wxT("TB_SETBUTTONINFO")); + } + else + { + // we successfully resized this one, move all the controls after it + // by the corresponding amount (may be positive or negative) + offset += tbbi.cx - (rcOld.right - rcOld.left); + } + } +#endif // TB_SETBUTTONINFO +} + // ---------------------------------------------------------------------------- // message handlers // ---------------------------------------------------------------------------- @@ -1188,9 +1325,10 @@ bool wxToolBar::MSWCommand(WXUINT WXUNUSED(cmd), WXWORD id_) bool toggled = false; // just to suppress warnings + LRESULT state = ::SendMessage(GetHwnd(), TB_GETSTATE, id, 0); + if ( tool->CanBeToggled() ) { - LRESULT state = ::SendMessage(GetHwnd(), TB_GETSTATE, id, 0); toggled = (state & TBSTATE_CHECKED) != 0; // ignore the event when a radio button is released, as this doesn't @@ -1202,9 +1340,38 @@ bool wxToolBar::MSWCommand(WXUINT WXUNUSED(cmd), WXWORD id_) UnToggleRadioGroup(tool); } + // Without the two lines of code below, if the toolbar was repainted during + // OnLeftClick(), then it could end up without the tool bitmap temporarily + // (see http://lists.nongnu.org/archive/html/lmi/2008-10/msg00014.html). + // The Update() call below ensures that this won't happen, by repainting + // invalidated areas of the toolbar immediately. + // + // To complicate matters, the tool would be drawn in depressed state (this + // code is called when mouse button is released, not pressed). That's not + // ideal, having the tool pressed for the duration of OnLeftClick() + // provides the user with useful visual clue that the app is busy reacting + // to the event. So we manually put the tool into pressed state, handle the + // event and then finally restore tool's original state. + ::SendMessage(GetHwnd(), TB_SETSTATE, id, MAKELONG(state | TBSTATE_PRESSED, 0)); + Update(); + + bool allowLeftClick = OnLeftClick(id, toggled); + + // Restore the unpressed state. Enabled/toggled state might have been + // changed since so take care of it. + if (tool->IsEnabled()) + state |= TBSTATE_ENABLED; + else + state &= ~TBSTATE_ENABLED; + if (tool->IsToggled()) + state |= TBSTATE_CHECKED; + else + state &= ~TBSTATE_CHECKED; + ::SendMessage(GetHwnd(), TB_SETSTATE, id, MAKELONG(state, 0)); + // OnLeftClick() can veto the button state change - for buttons which - // may be toggled only, of couse - if ( !OnLeftClick(id, toggled) && tool->CanBeToggled() ) + // may be toggled only, of course. + if ( !allowLeftClick && tool->CanBeToggled() ) { // revert back tool->Toggle(!toggled); @@ -1224,7 +1391,7 @@ bool wxToolBar::MSWOnNotify(int WXUNUSED(idCtrl), { LPNMTOOLBAR tbhdr = (LPNMTOOLBAR)lParam; - wxCommandEvent evt(wxEVT_COMMAND_TOOL_DROPDOWN_CLICKED, tbhdr->iItem); + wxCommandEvent evt(wxEVT_TOOL_DROPDOWN, tbhdr->iItem); if ( HandleWindowEvent(evt) ) { // Event got handled, don't display default popup menu @@ -1232,15 +1399,15 @@ bool wxToolBar::MSWOnNotify(int WXUNUSED(idCtrl), } const wxToolBarToolBase * const tool = FindById(tbhdr->iItem); - wxCHECK_MSG( tool, false, _T("drop down message for unknown tool") ); + wxCHECK_MSG( tool, false, wxT("drop down message for unknown tool") ); wxMenu * const menu = tool->GetDropdownMenu(); if ( !menu ) return false; // Display popup menu below button - RECT r; - if (::SendMessage(GetHwnd(), TB_GETITEMRECT, GetToolPos(tbhdr->iItem), (LPARAM)&r)) + const RECT r = wxGetTBItemRect(GetHwnd(), GetToolPos(tbhdr->iItem)); + if ( r.right ) PopupMenu(menu, r.left, r.bottom); return true; @@ -1362,7 +1529,7 @@ wxToolBarToolBase *wxToolBar::FindToolForPosition(wxCoord x, wxCoord y) const // TB_HITTEST returns m_nButtons ( not -1 ) if ( index < 0 || (size_t)index >= m_nButtons ) // it's a separator or there is no tool at all there - return (wxToolBarToolBase *)NULL; + return NULL; // when TB_SETBUTTONINFO is available (both during compile- and run-time), // we don't use the dummy separators hack @@ -1390,11 +1557,7 @@ void wxToolBar::UpdateSize() // toolbar to full width again, but only if the parent is a frame and the // toolbar is managed by the frame. Otherwise assume that some other // layout mechanism is controlling the toolbar size and leave it alone. - wxFrame *frame = wxDynamicCast(GetParent(), wxFrame); - if ( frame && frame->GetToolBar() == this ) - { - frame->SendSizeEvent(); - } + SendSizeEventToParent(); } // ---------------------------------------------------------------------------- @@ -1449,12 +1612,12 @@ void wxToolBar::DoSetToggle(wxToolBarToolBase *WXUNUSED(tool), bool WXUNUSED(tog { // VZ: AFAIK, the button has to be created either with TBSTYLE_CHECK or // without, so we really need to delete the button and recreate it here - wxFAIL_MSG( _T("not implemented") ); + wxFAIL_MSG( wxT("not implemented") ); } void wxToolBar::SetToolNormalBitmap( int id, const wxBitmap& bitmap ) { - wxToolBarTool* tool = wx_static_cast(wxToolBarTool*, FindById(id)); + wxToolBarTool* tool = static_cast(FindById(id)); if ( tool ) { wxCHECK_RET( tool->IsButton(), wxT("Can only set bitmap on button tools.")); @@ -1466,7 +1629,7 @@ void wxToolBar::SetToolNormalBitmap( int id, const wxBitmap& bitmap ) void wxToolBar::SetToolDisabledBitmap( int id, const wxBitmap& bitmap ) { - wxToolBarTool* tool = wx_static_cast(wxToolBarTool*, FindById(id)); + wxToolBarTool* tool = static_cast(FindById(id)); if ( tool ) { wxCHECK_RET( tool->IsButton(), wxT("Can only set bitmap on button tools.")); @@ -1528,283 +1691,225 @@ void wxToolBar::OnMouseEvent(wxMouseEvent& event) } } -// This handler is required to allow the toolbar to be set to a non-default -// colour: for example, when it must blend in with a notebook page. +// This handler is needed to fix problems with painting the background of +// toolbar icons with comctl32.dll < 6.0. void wxToolBar::OnEraseBackground(wxEraseEvent& event) { - RECT rect = wxGetClientRect(GetHwnd()); - - wxDC *dc = event.GetDC(); - if (!dc) return; - wxMSWDCImpl *impl = (wxMSWDCImpl*) dc->GetImpl(); - HDC hdc = GetHdcOf(*impl); +#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK + MSWDoEraseBackground(event.GetDC()->GetHDC()); +#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK +} - int majorVersion, minorVersion; - wxGetOsVersion(& majorVersion, & minorVersion); +bool wxToolBar::HandleSize(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam) +{ + // wait until we have some tools + if ( !GetToolsCount() ) + return false; -#if wxUSE_UXTHEME - // we may need to draw themed colour so that we appear correctly on - // e.g. notebook page under XP with themes but only do it if the parent - // draws themed background itself - if ( !UseBgCol() && !GetParent()->UseBgCol() ) + // calculate our minor dimension ourselves - we're confusing the standard + // logic (TB_AUTOSIZE) with our horizontal toolbars and other hacks + const RECT r = wxGetTBItemRect(GetHwnd(), 0); + if ( !r.right ) + return false; + + int w, h; + + if ( IsVertical() ) { - wxUxThemeEngine *theme = wxUxThemeEngine::GetIfActive(); - if ( theme ) + w = r.right - r.left; + if ( m_maxRows ) { - HRESULT - hr = theme->DrawThemeParentBackground(GetHwnd(), hdc, &rect); - if ( hr == S_OK ) - return; - - // it can also return S_FALSE which seems to simply say that it - // didn't draw anything but no error really occurred - if ( FAILED(hr) ) - wxLogApiError(_T("DrawThemeParentBackground(toolbar)"), hr); + w *= (m_nButtons + m_maxRows - 1)/m_maxRows; } + h = HIWORD(lParam); } - - // Only draw a rebar theme on Vista, since it doesn't jive so well with XP - if ( !UseBgCol() && majorVersion >= 6 ) + else { - wxUxThemeEngine *theme = wxUxThemeEngine::GetIfActive(); - if ( theme ) + w = LOWORD(lParam); + if (HasFlag( wxTB_FLAT )) + h = r.bottom - r.top - 3; + else + h = r.bottom - r.top; + if ( m_maxRows ) { - wxUxThemeHandle hTheme(this, L"REBAR"); - - RECT r; - wxRect rect = GetClientRect(); - wxCopyRectToRECT(rect, r); - - HRESULT hr = theme->DrawThemeBackground(hTheme, hdc, 0, 0, & r, NULL); - if ( hr == S_OK ) - return; - - // it can also return S_FALSE which seems to simply say that it - // didn't draw anything but no error really occurred - if ( FAILED(hr) ) - wxLogApiError(_T("DrawThemeParentBackground(toolbar)"), hr); + // FIXME: hardcoded separator line height... + h += HasFlag(wxTB_NODIVIDER) ? 4 : 6; + h *= m_maxRows; } } -#endif // wxUSE_UXTHEME - - // we need to always draw our background under XP, as otherwise it doesn't - // appear correctly with some themes (e.g. Zune one) - if ( majorVersion == 5 || - UseBgCol() || (GetMSWToolbarStyle() & TBSTYLE_TRANSPARENT) ) + if ( MAKELPARAM(w, h) != lParam ) { - // do draw our background - // - // notice that this 'dumb' implementation may cause flicker for some of - // the controls in which case they should intercept wxEraseEvent and - // process it themselves somehow - AutoHBRUSH hBrush(wxColourToRGB(GetBackgroundColour())); - - wxCHANGE_HDC_MAP_MODE(hdc, MM_TEXT); - ::FillRect(hdc, &rect, hBrush); - } - else // we have no non default background colour - { - // let the system do it for us - event.Skip(); + // size really changed + SetSize(w, h); } + + UpdateStretchableSpacersSize(); + + // message processed + return true; } -bool wxToolBar::HandleSize(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam) +#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK + +bool wxToolBar::HandlePaint(WXWPARAM wParam, WXLPARAM lParam) { - // calculate our minor dimension ourselves - we're confusing the standard - // logic (TB_AUTOSIZE) with our horizontal toolbars and other hacks - RECT r; - if ( ::SendMessage(GetHwnd(), TB_GETITEMRECT, 0, (LPARAM)&r) ) + // we must prevent the dummy separators corresponding to controls or + // stretchable spaces from being seen: we used to do it by painting over + // them but this, unsurprisingly, resulted in a lot of flicker so now we + // prevent the toolbar from painting them at all + + // compute the region containing all dummy separators which we don't want + // to be seen + wxRegion rgnDummySeps; + const wxRect rectTotal = GetClientRect(); + int toolIndex = 0; + for ( wxToolBarToolsList::compatibility_iterator node = m_tools.GetFirst(); + node; + node = node->GetNext() ) { - int w, h; + wxToolBarTool * const + tool = static_cast(node->GetData()); - if ( IsVertical() ) + if ( tool->IsControl() || tool->IsStretchableSpace() ) { - w = r.right - r.left; - if ( m_maxRows ) + const size_t numSeps = tool->GetSeparatorsCount(); + for ( size_t n = 0; n < numSeps; n++, toolIndex++ ) { - w *= (m_nButtons + m_maxRows - 1)/m_maxRows; + // for some reason TB_GETITEMRECT returns a rectangle 1 pixel + // shorter than the full window size (at least under Windows 7) + // but we need to erase the full width/height below + RECT rcItem = wxGetTBItemRect(GetHwnd(), toolIndex); + if ( IsVertical() ) + { + rcItem.left = 0; + rcItem.right = rectTotal.width; + } + else + { + rcItem.top = 0; + rcItem.bottom = rectTotal.height; + } + + rgnDummySeps.Union(wxRectFromRECT(rcItem)); } - h = HIWORD(lParam); } else { - w = LOWORD(lParam); - if (HasFlag( wxTB_FLAT )) - h = r.bottom - r.top - 3; - else - h = r.bottom - r.top; - if ( m_maxRows ) - { - // FIXME: hardcoded separator line height... - h += HasFlag(wxTB_NODIVIDER) ? 4 : 6; - h *= m_maxRows; - } - } - - if ( MAKELPARAM(w, h) != lParam ) - { - // size really changed - SetSize(w, h); + // normal tools never correspond to more than one native button + toolIndex++; } - - // message processed - return true; } - return false; -} - -bool wxToolBar::HandlePaint(WXWPARAM wParam, WXLPARAM lParam) -{ - // erase any dummy separators which were used - // for aligning the controls if any here - - // first of all, are there any controls at all? - wxToolBarToolsList::compatibility_iterator node; - for ( node = m_tools.GetFirst(); node; node = node->GetNext() ) + if ( rgnDummySeps.IsOk() ) { - if ( node->GetData()->IsControl() ) - break; + // exclude the area occupied by the controls and stretchable spaces + // from the update region to prevent the toolbar from drawing + // separators in it + if ( !::ValidateRgn(GetHwnd(), GetHrgnOf(rgnDummySeps)) ) + { + wxLogLastError(wxT("ValidateRgn()")); + } } - if ( !node ) - // no controls, nothing to erase - return false; - - wxSize clientSize = GetClientSize(); - int majorVersion, minorVersion; - wxGetOsVersion(& majorVersion, & minorVersion); - - // prepare the DC on which we'll be drawing - wxClientDC dc(this); - dc.SetBrush(wxBrush(GetBackgroundColour(), wxSOLID)); - dc.SetPen(*wxTRANSPARENT_PEN); - - RECT r; - if ( !::GetUpdateRect(GetHwnd(), &r, FALSE) ) - // nothing to redraw anyhow - return false; + // still let the native control draw everything else normally but set up a + // hook to be able to process the next WM_ERASEBKGND sent to our parent + // because toolbar will ask it to erase its background from its WM_PAINT + // handler (when using TBSTYLE_TRANSPARENT which we do always use) + // + // installing hook is not completely trivial as all kinds of strange + // situations are possible: sometimes we can be called recursively from + // inside the native toolbar WM_PAINT handler so the hook might already be + // installed and sometimes the native toolbar might not send WM_ERASEBKGND + // to the parent at all for whatever reason, so deal with all these cases + wxWindow * const parent = GetParent(); + const bool hadHook = parent->MSWHasEraseBgHook(); + if ( !hadHook ) + GetParent()->MSWSetEraseBgHook(this); - wxRect rectUpdate; - wxCopyRECTToRect(r, rectUpdate); + MSWDefWindowProc(WM_PAINT, wParam, lParam); - dc.SetClippingRegion(rectUpdate); + if ( !hadHook ) + GetParent()->MSWSetEraseBgHook(NULL); - // draw the toolbar tools, separators &c normally - wxControl::MSWWindowProc(WM_PAINT, wParam, lParam); - // for each control in the toolbar find all the separators intersecting it - // and erase them - // - // NB: this is really the only way to do it as we don't know if a separator - // corresponds to a control (i.e. is a dummy one) or a real one - // otherwise - for ( node = m_tools.GetFirst(); node; node = node->GetNext() ) + if ( rgnDummySeps.IsOk() ) { - wxToolBarTool *tool = (wxToolBarTool*)node->GetData(); - if ( tool->IsControl() ) - { - // get the control rect in our client coords - wxControl *control = tool->GetControl(); - wxStaticText *staticText = tool->GetStaticText(); - wxRect rectCtrl = control->GetRect(); - wxRect rectStaticText(0,0,0,0); - if ( staticText ) - { - rectStaticText = staticText->GetRect(); - } - - // iterate over all buttons - TBBUTTON tbb; - int count = ::SendMessage(GetHwnd(), TB_BUTTONCOUNT, 0, 0); - for ( int n = 0; n < count; n++ ) - { - // is it a separator? - if ( !::SendMessage(GetHwnd(), TB_GETBUTTON, - n, (LPARAM)&tbb) ) - { - wxLogDebug(_T("TB_GETBUTTON failed?")); - - continue; - } + // erase the dummy separators region ourselves now as nobody painted + // over them + WindowHDC hdc(GetHwnd()); + ::SelectClipRgn(hdc, GetHrgnOf(rgnDummySeps)); + MSWDoEraseBackground(hdc); + } - if ( tbb.fsStyle != TBSTYLE_SEP ) - continue; + return true; +} - // get the bounding rect of the separator - RECT r; - if ( !::SendMessage(GetHwnd(), TB_GETITEMRECT, - n, (LPARAM)&r) ) - { - wxLogDebug(_T("TB_GETITEMRECT failed?")); +WXHBRUSH wxToolBar::MSWGetToolbarBgBrush() +{ + // we conservatively use a solid brush here but we could also use a themed + // brush by using DrawThemeBackground() to create a bitmap brush (it'd need + // to be invalidated whenever the toolbar is resized and, also, correctly + // aligned using SetBrushOrgEx() before each use -- there is code for doing + // this in wxNotebook already so it'd need to be refactored into wxWindow) + // + // however inasmuch as there is a default background for the toolbar at all + // (and this is not a trivial question as different applications use very + // different colours), it seems to be a solid one and using REBAR + // background brush as we used to do before doesn't look good at all under + // Windows 7 (and probably Vista too), so for now we just keep it simple + wxColour const + colBg = m_hasBgCol ? GetBackgroundColour() + : wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE); + wxBrush * const + brush = wxTheBrushList->FindOrCreateBrush(colBg); + + return brush ? static_cast(brush->GetResourceHandle()) : 0; +} - continue; - } +WXHBRUSH wxToolBar::MSWGetBgBrushForChild(WXHDC hDC, wxWindowMSW *child) +{ + WXHBRUSH hbr = wxToolBarBase::MSWGetBgBrushForChild(hDC, child); + if ( hbr ) + return hbr; - // does it intersect the control? - wxRect rectItem; - wxCopyRECTToRect(r, rectItem); - if ( rectCtrl.Intersects(rectItem) || (staticText && rectStaticText.Intersects(rectItem))) - { - // yes, do erase it! + // the base class version only returns a brush for erasing children + // background if we have a non-default background colour but as the toolbar + // doesn't erase its own background by default, we need to always do it for + // (semi-)transparent children + if ( child->GetParent() == this && child->HasTransparentBackground() ) + return MSWGetToolbarBgBrush(); - bool haveRefreshed = false; + return 0; +} -#if wxUSE_UXTHEME - if ( !UseBgCol() && !GetParent()->UseBgCol() ) - { - // Don't use DrawThemeBackground - } - else if ( !UseBgCol() && majorVersion >= 6 ) - { - wxUxThemeEngine *theme = wxUxThemeEngine::GetIfActive(); - if ( theme ) - { - wxUxThemeHandle hTheme(this, L"REBAR"); - - RECT clipRect = r; - - // Draw the whole background since the pattern may be position sensitive; - // but clip it to the area of interest. - r.left = 0; - r.right = clientSize.x; - r.top = 0; - r.bottom = clientSize.y; - - wxMSWDCImpl *impl = (wxMSWDCImpl*) dc.GetImpl(); - HRESULT hr = theme->DrawThemeBackground(hTheme, GetHdcOf(*impl), 0, 0, & r, & clipRect); - if ( hr == S_OK ) - haveRefreshed = true; - } - } -#endif +void wxToolBar::MSWDoEraseBackground(WXHDC hDC) +{ + wxFillRect(GetHwnd(), (HDC)hDC, (HBRUSH)MSWGetToolbarBgBrush()); +} - if (!haveRefreshed) - dc.DrawRectangle(rectItem); - } +bool wxToolBar::MSWEraseBgHook(WXHDC hDC) +{ + // toolbar WM_PAINT handler offsets the DC origin before sending + // WM_ERASEBKGND to the parent but as we handle it in the toolbar itself, + // we need to reset it back + HDC hdc = (HDC)hDC; + POINT ptOldOrg; + if ( !::SetWindowOrgEx(hdc, 0, 0, &ptOldOrg) ) + { + wxLogLastError(wxT("SetWindowOrgEx(tbar-bg-hdc)")); + return false; + } - if ( rectCtrl.Intersects(rectItem) ) - { - // Necessary in case we use a no-paint-on-size - // style in the parent: the controls can disappear - control->Refresh(false); - } + MSWDoEraseBackground(hDC); - if ( staticText && rectStaticText.Intersects(rectItem) ) - { - // Necessary in case we use a no-paint-on-size - // style in the parent: the controls can disappear - staticText->Refresh(false); - } - } - } - } + ::SetWindowOrgEx(hdc, ptOldOrg.x, ptOldOrg.y, NULL); return true; } +#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK + void wxToolBar::HandleMouseMove(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam) { wxCoord x = GET_X_LPARAM(lParam), @@ -1834,14 +1939,16 @@ WXLRESULT wxToolBar::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam return 0; break; -#ifndef __WXWINCE__ +#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK case WM_PAINT: - if ( HandlePaint(wParam, lParam) ) + // refreshing the controls in the toolbar inside a composite window + // results in an endless stream of WM_PAINT messages -- and seems + // to be unnecessary anyhow as everything works just fine without + // any special workarounds in this case + if ( !IsDoubleBuffered() && HandlePaint(wParam, lParam) ) return 0; -#endif - - default: break; +#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK } return wxControl::MSWWindowProc(nMsg, wParam, lParam); @@ -1859,7 +1966,7 @@ WXHBITMAP wxToolBar::MapBitmap(WXHBITMAP bitmap, int width, int height) if ( !hdcMem ) { - wxLogLastError(_T("CreateCompatibleDC")); + wxLogLastError(wxT("CreateCompatibleDC")); return bitmap; } @@ -1868,7 +1975,7 @@ WXHBITMAP wxToolBar::MapBitmap(WXHBITMAP bitmap, int width, int height) if ( !bmpInHDC ) { - wxLogLastError(_T("SelectObject")); + wxLogLastError(wxT("SelectObject")); return bitmap; }