]> git.saurik.com Git - wxWidgets.git/blobdiff - src/msw/toolbar.cpp
Add wxGrid::Render() for drawing the grid to any wxDC.
[wxWidgets.git] / src / msw / toolbar.cpp
index 71aec08351b82a5c904c6c1c8be083493d6f7f5b..697b9c6c8d67fe8cfbd631109656f58be0287a84 100644 (file)
@@ -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
@@ -126,6 +127,7 @@ IMPLEMENT_DYNAMIC_CLASS(wxToolBar, wxControl)
 BEGIN_EVENT_TABLE(wxToolBar, wxToolBarBase)
     EVT_MOUSE_EVENTS(wxToolBar::OnMouseEvent)
     EVT_SYS_COLOUR_CHANGED(wxToolBar::OnSysColourChanged)
+    EVT_ERASE_BACKGROUND(wxToolBar::OnEraseBackground)
 END_EVENT_TABLE()
 
 // ----------------------------------------------------------------------------
@@ -335,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;
 }
@@ -410,11 +409,7 @@ void wxToolBar::Recreate()
         m_hBitmap = 0;
     }
 
-    if ( m_disabledImgList )
-    {
-        delete m_disabledImgList;
-        m_disabledImgList = NULL;
-    }
+    wxDELETE(m_disabledImgList);
 
     Realize();
 }
@@ -500,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;
@@ -531,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;
 }
 
@@ -578,14 +572,18 @@ bool wxToolBar::DoDeleteTool(size_t pos, wxToolBarToolBase *tool)
     // get the size of the button we're going to delete
     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 )
@@ -598,14 +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() )
         {
-            tool2->MoveBy(-width);
+            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();
+
+            if ( tool2->IsControl() )
+            {
+                tool2->MoveBy(-delta);
+            }
+
+            // if a stretch spacer is found just redistribute the available space
+            else if ( tool2->IsStretchable() )
+            {
+                UpdateStretchableSpacersSize();
+                break;
+            }
         }
     }
 
@@ -616,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
@@ -632,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()
                                         );
@@ -757,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);
@@ -775,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
@@ -1225,13 +1251,13 @@ void wxToolBar::UpdateStretchableSpacersSize()
     // 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 : 0;
+    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
-                                : 0;
+                                : 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
@@ -1309,7 +1335,7 @@ bool wxToolBar::MSWCommand(WXUINT WXUNUSED(cmd), WXWORD id_)
     // 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 bellow ensures that this won't happen, by repainting
+    // 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
@@ -1336,7 +1362,7 @@ bool wxToolBar::MSWCommand(WXUINT WXUNUSED(cmd), WXWORD id_)
     ::SendMessage(GetHwnd(), TB_SETSTATE, id, MAKELONG(state, 0));
 
     // OnLeftClick() can veto the button state change - for buttons which
-    // may be toggled only, of couse
+    // may be toggled only, of course.
     if ( !allowLeftClick && tool->CanBeToggled() )
     {
         // revert back
@@ -1657,6 +1683,13 @@ void wxToolBar::OnMouseEvent(wxMouseEvent& event)
     }
 }
 
+// This handler is needed to fix problems with painting the background of
+// toolbar icons with comctl32.dll < 6.0.
+void wxToolBar::OnEraseBackground(wxEraseEvent& event)
+{
+    MSWDoEraseBackground(event.GetDC()->GetHDC());
+}
+
 bool wxToolBar::HandleSize(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam)
 {
     // wait until we have some tools
@@ -1707,12 +1740,19 @@ bool wxToolBar::HandleSize(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam)
     return true;
 }
 
-#ifndef __WXWINCE__
+#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
 
-bool wxToolBar::HandlePaint(WXWPARAM WXUNUSED(wParam), WXLPARAM WXUNUSED(lParam))
+bool wxToolBar::HandlePaint(WXWPARAM wParam, WXLPARAM lParam)
 {
-    // exclude the area occupied by the controls and stretchable spaces from
-    // the update region to prevent the toolbar from drawing separators in it
+    // 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;
@@ -1726,13 +1766,22 @@ bool wxToolBar::HandlePaint(WXWPARAM WXUNUSED(wParam), WXLPARAM WXUNUSED(lParam)
             const size_t numSeps = tool->GetSeparatorsCount();
             for ( size_t n = 0; n < numSeps; n++, toolIndex++ )
             {
-                const RECT rcItem = wxGetTBItemRect(GetHwnd(), toolIndex);
-
-                const wxRegion rgnItem(wxRectFromRECT(rcItem));
-                if ( !ValidateRgn(GetHwnd(), GetHrgnOf(rgnItem)) )
+                // 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() )
                 {
-                    wxLogLastError(wxT("ValidateRgn()"));
+                    rcItem.left = 0;
+                    rcItem.right = rectTotal.width;
                 }
+                else
+                {
+                    rcItem.top = 0;
+                    rcItem.bottom = rectTotal.height;
+                }
+
+                rgnDummySeps.Union(wxRectFromRECT(rcItem));
             }
         }
         else
@@ -1742,10 +1791,114 @@ bool wxToolBar::HandlePaint(WXWPARAM WXUNUSED(wParam), WXLPARAM WXUNUSED(lParam)
         }
     }
 
-    // still let the native control draw everything else normally
-    return false;
+    if ( rgnDummySeps.IsOk() )
+    {
+        // 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()"));
+        }
+    }
+
+    // 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);
+
+    MSWDefWindowProc(WM_PAINT, wParam, lParam);
+
+    if ( !hadHook )
+        GetParent()->MSWSetEraseBgHook(NULL);
+
+
+    if ( rgnDummySeps.IsOk() )
+    {
+        // erase the dummy separators region ourselves now as nobody painted
+        // over them
+        WindowHDC hdc(GetHwnd());
+        ::SelectClipRgn(hdc, GetHrgnOf(rgnDummySeps));
+        MSWDoEraseBackground(hdc);
+    }
+
+    return true;
+}
+
+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<WXHBRUSH>(brush->GetResourceHandle()) : 0;
+}
+
+WXHBRUSH wxToolBar::MSWGetBgBrushForChild(WXHDC hDC, wxWindowMSW *child)
+{
+    WXHBRUSH hbr = wxToolBarBase::MSWGetBgBrushForChild(hDC, child);
+    if ( hbr )
+        return hbr;
+
+    // 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();
+
+    return 0;
+}
+
+void wxToolBar::MSWDoEraseBackground(WXHDC hDC)
+{
+    wxFillRect(GetHwnd(), (HDC)hDC, (HBRUSH)MSWGetToolbarBgBrush());
+}
+
+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;
+    }
+
+    MSWDoEraseBackground(hDC);
+
+    ::SetWindowOrgEx(hdc, ptOldOrg.x, ptOldOrg.y, NULL);
+
+    return true;
 }
-#endif // __WXWINCE__
+
+#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
 
 void wxToolBar::HandleMouseMove(WXWPARAM WXUNUSED(wParam), WXLPARAM lParam)
 {
@@ -1776,7 +1929,7 @@ WXLRESULT wxToolBar::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam
                 return 0;
             break;
 
-#ifndef __WXWINCE__
+#ifdef wxHAS_MSW_BACKGROUND_ERASE_HOOK
         case WM_PAINT:
             // refreshing the controls in the toolbar inside a composite window
             // results in an endless stream of WM_PAINT messages -- and seems
@@ -1785,7 +1938,7 @@ WXLRESULT wxToolBar::MSWWindowProc(WXUINT nMsg, WXWPARAM wParam, WXLPARAM lParam
             if ( !IsDoubleBuffered() && HandlePaint(wParam, lParam) )
                 return 0;
             break;
-#endif // __WXWINCE__
+#endif // wxHAS_MSW_BACKGROUND_ERASE_HOOK
     }
 
     return wxControl::MSWWindowProc(nMsg, wParam, lParam);