]> git.saurik.com Git - wxWidgets.git/blobdiff - src/ribbon/bar.cpp
Merged GSOC Ribbon work from SOC2009_RIBBON branch into trunk.
[wxWidgets.git] / src / ribbon / bar.cpp
diff --git a/src/ribbon/bar.cpp b/src/ribbon/bar.cpp
new file mode 100644 (file)
index 0000000..3aa3477
--- /dev/null
@@ -0,0 +1,964 @@
+///////////////////////////////////////////////////////////////////////////////
+// Name:        src/ribbon/bar.cpp
+// Purpose:     Top-level component of the ribbon-bar-style interface
+// Author:      Peter Cawley
+// Modified by:
+// Created:     2009-05-23
+// RCS-ID:      $Id$
+// Copyright:   (C) Peter Cawley
+// Licence:     wxWindows licence
+///////////////////////////////////////////////////////////////////////////////
+
+#include "wx/wxprec.h"
+
+#ifdef __BORLANDC__
+    #pragma hdrstop
+#endif
+
+#include "wx/ribbon/bar.h"
+
+#if wxUSE_RIBBON
+
+#include "wx/ribbon/art.h"
+#include "wx/dcbuffer.h"
+
+#ifndef WX_PRECOMP
+#endif
+
+#ifdef __WXMSW__
+#include "wx/msw/private.h"
+#endif
+
+#include "wx/arrimpl.cpp"
+
+WX_DEFINE_USER_EXPORTED_OBJARRAY(wxRibbonPageTabInfoArray);
+
+wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_PAGE_CHANGED, wxRibbonBarEvent);
+wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_PAGE_CHANGING, wxRibbonBarEvent);
+wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_TAB_MIDDLE_DOWN, wxRibbonBarEvent);
+wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_TAB_MIDDLE_UP, wxRibbonBarEvent);
+wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_TAB_RIGHT_DOWN, wxRibbonBarEvent);
+wxDEFINE_EVENT(wxEVT_COMMAND_RIBBONBAR_TAB_RIGHT_UP, wxRibbonBarEvent);
+
+IMPLEMENT_CLASS(wxRibbonBar, wxRibbonControl)
+IMPLEMENT_DYNAMIC_CLASS(wxRibbonBarEvent, wxNotifyEvent)
+
+BEGIN_EVENT_TABLE(wxRibbonBar, wxRibbonControl)
+  EVT_ERASE_BACKGROUND(wxRibbonBar::OnEraseBackground)
+  EVT_LEAVE_WINDOW(wxRibbonBar::OnMouseLeave)
+  EVT_LEFT_DOWN(wxRibbonBar::OnMouseLeftDown)
+  EVT_LEFT_UP(wxRibbonBar::OnMouseLeftUp)
+  EVT_MIDDLE_DOWN(wxRibbonBar::OnMouseMiddleDown)
+  EVT_MIDDLE_UP(wxRibbonBar::OnMouseMiddleUp)
+  EVT_MOTION(wxRibbonBar::OnMouseMove)
+  EVT_PAINT(wxRibbonBar::OnPaint)
+  EVT_RIGHT_DOWN(wxRibbonBar::OnMouseRightDown)
+  EVT_RIGHT_UP(wxRibbonBar::OnMouseRightUp)
+  EVT_SIZE(wxRibbonBar::OnSize)
+END_EVENT_TABLE()
+
+void wxRibbonBar::AddPage(wxRibbonPage *page)
+{
+    wxRibbonPageTabInfo info;
+
+    info.page = page;
+    info.active = false;
+    info.hovered = false;
+    // info.rect not set (intentional)
+
+    wxMemoryDC dcTemp;
+    wxString label = wxEmptyString;
+    if(m_flags & wxRIBBON_BAR_SHOW_PAGE_LABELS)
+        label = page->GetLabel();
+    wxBitmap icon = wxNullBitmap;
+    if(m_flags & wxRIBBON_BAR_SHOW_PAGE_ICONS)
+        icon = page->GetIcon();
+    m_art->GetBarTabWidth(dcTemp, this, label, icon,
+                          &info.ideal_width,
+                          &info.small_begin_need_separator_width,
+                          &info.small_must_have_separator_width,
+                          &info.minimum_width);
+
+    if(m_pages.IsEmpty())
+    {
+        m_tabs_total_width_ideal = info.ideal_width;
+        m_tabs_total_width_minimum = info.minimum_width;
+    }
+    else
+    {
+        int sep = m_art->GetMetric(wxRIBBON_ART_TAB_SEPARATION_SIZE);
+        m_tabs_total_width_ideal += sep + info.ideal_width;
+        m_tabs_total_width_minimum += sep + info.minimum_width;
+    }
+    m_pages.Add(info);
+
+    page->Hide(); // Most likely case is that this new page is not the active tab
+    page->SetArtProvider(m_art);
+
+    if(m_pages.GetCount() == 1)
+    {
+        SetActivePage((size_t)0);
+    }
+}
+
+bool wxRibbonBar::DismissExpandedPanel()
+{
+    if(m_current_page == -1)
+        return false;
+    return m_pages.Item(m_current_page).page->DismissExpandedPanel();
+}
+
+void wxRibbonBar::SetWindowStyleFlag(long style)
+{
+    m_flags = style;
+    if(m_art)
+        m_art->SetFlags(style);
+}
+
+long wxRibbonBar::GetWindowStyleFlag() const
+{
+    return m_flags;
+}
+
+bool wxRibbonBar::Realize()
+{
+    bool status = true;
+
+    wxMemoryDC dcTemp;
+    int sep = m_art->GetMetric(wxRIBBON_ART_TAB_SEPARATION_SIZE);
+    size_t numtabs = m_pages.GetCount();
+    size_t i;
+    for(i = 0; i < numtabs; ++i)
+    {
+        wxRibbonPageTabInfo& info = m_pages.Item(i);
+        RepositionPage(info.page);
+        if(!info.page->Realize())
+        {
+            status = false;
+        }
+        wxString label = wxEmptyString;
+        if(m_flags & wxRIBBON_BAR_SHOW_PAGE_LABELS)
+            label = info.page->GetLabel();
+        wxBitmap icon = wxNullBitmap;
+        if(m_flags & wxRIBBON_BAR_SHOW_PAGE_ICONS)
+            icon = info.page->GetIcon();
+        m_art->GetBarTabWidth(dcTemp, this, label, icon,
+                              &info.ideal_width,
+                              &info.small_begin_need_separator_width,
+                              &info.small_must_have_separator_width,
+                              &info.minimum_width);
+
+        if(i == 0)
+        {
+            m_tabs_total_width_ideal = info.ideal_width;
+            m_tabs_total_width_minimum = info.minimum_width;
+        }
+        else
+        {
+            m_tabs_total_width_ideal += sep + info.ideal_width;
+            m_tabs_total_width_minimum += sep + info.minimum_width;
+        }
+    }
+    m_tab_height = m_art->GetTabCtrlHeight(dcTemp, this, m_pages);
+
+    RecalculateMinSize();
+    RecalculateTabSizes();
+    Refresh();
+
+    return status;
+}
+
+void wxRibbonBar::OnMouseMove(wxMouseEvent& evt)
+{
+    int x = evt.GetX();
+    int y = evt.GetY();
+    int hovered_page = -1;
+    bool refresh_tabs = false;
+    if(y < m_tab_height)
+    {
+        // It is quite likely that the mouse moved a small amount and is still over the same tab
+        if(m_current_hovered_page != -1 && m_pages.Item((size_t)m_current_hovered_page).rect.Contains(x, y))
+        {
+            hovered_page = m_current_hovered_page;
+            // But be careful, if tabs can be scrolled, then parts of the tab rect may not be valid
+            if(m_tab_scroll_buttons_shown)
+            {
+                if(x >= m_tab_scroll_right_button_rect.GetX() || x < m_tab_scroll_left_button_rect.GetRight())
+                {
+                    hovered_page = -1;
+                }
+            }
+        }
+        else
+        {
+            HitTestTabs(evt.GetPosition(), &hovered_page);
+        }
+    }
+    if(hovered_page != m_current_hovered_page)
+    {
+        if(m_current_hovered_page != -1)
+        {
+            m_pages.Item((int)m_current_hovered_page).hovered = false;
+        }
+        m_current_hovered_page = hovered_page;
+        if(m_current_hovered_page != -1)
+        {
+            m_pages.Item((int)m_current_hovered_page).hovered = true;
+        }
+        refresh_tabs = true;
+    }
+    if(m_tab_scroll_buttons_shown)
+    {
+#define SET_FLAG(variable, flag) \
+    { if(((variable) & (flag)) != (flag)) variable |= (flag), refresh_tabs = true; }
+#define UNSET_FLAG(variable, flag) \
+    { if((variable) & (flag)) variable &= ~(flag), refresh_tabs = true; }
+
+        if(m_tab_scroll_left_button_rect.Contains(x, y))
+            SET_FLAG(m_tab_scroll_left_button_state, wxRIBBON_SCROLL_BTN_HOVERED)
+        else
+            UNSET_FLAG(m_tab_scroll_left_button_state, wxRIBBON_SCROLL_BTN_HOVERED)
+
+        if(m_tab_scroll_right_button_rect.Contains(x, y))
+            SET_FLAG(m_tab_scroll_right_button_state, wxRIBBON_SCROLL_BTN_HOVERED)
+        else
+            UNSET_FLAG(m_tab_scroll_right_button_state, wxRIBBON_SCROLL_BTN_HOVERED)
+#undef SET_FLAG
+#undef UNSET_FLAG
+    }
+    if(refresh_tabs)
+    {
+        RefreshTabBar();
+    }
+}
+
+void wxRibbonBar::OnMouseLeave(wxMouseEvent& WXUNUSED(evt))
+{
+    // The ribbon bar is (usually) at the top of a window, and at least on MSW, the mouse
+    // can leave the window quickly and leave a tab in the hovered state.
+    bool refresh_tabs = false;
+    if(m_current_hovered_page != -1)
+    {
+        m_pages.Item((int)m_current_hovered_page).hovered = false;
+        m_current_hovered_page = -1;
+        refresh_tabs = true;
+    }
+    if(m_tab_scroll_left_button_state & wxRIBBON_SCROLL_BTN_HOVERED)
+    {
+        m_tab_scroll_left_button_state &= ~wxRIBBON_SCROLL_BTN_HOVERED;
+        refresh_tabs = true;
+    }
+    if(m_tab_scroll_right_button_state & wxRIBBON_SCROLL_BTN_HOVERED)
+    {
+        m_tab_scroll_right_button_state &= ~wxRIBBON_SCROLL_BTN_HOVERED;
+        refresh_tabs = true;
+    }
+    if(refresh_tabs)
+    {
+        RefreshTabBar();
+    }
+}
+
+wxRibbonPage* wxRibbonBar::GetPage(int n)
+{
+    if(n < 0 || (size_t)n >= m_pages.GetCount())
+        return 0;
+    return m_pages.Item(n).page;
+}
+
+bool wxRibbonBar::SetActivePage(size_t page)
+{
+    if(m_current_page == (int)page)
+    {
+        return true;
+    }
+
+    if(page >= m_pages.GetCount())
+    {
+        return false;
+    }
+
+    if(m_current_page != -1)
+    {
+        m_pages.Item((size_t)m_current_page).active = false;
+        m_pages.Item((size_t)m_current_page).page->Hide();
+    }
+    m_current_page = (int)page;
+    m_pages.Item(page).active = true;
+    {
+        wxRibbonPage* wnd = m_pages.Item(page).page;
+        RepositionPage(wnd);
+        wnd->Layout();
+        wnd->Show();
+    }
+    Refresh();
+
+    return true;
+}
+
+bool wxRibbonBar::SetActivePage(wxRibbonPage* page)
+{
+    size_t numpages = m_pages.GetCount();
+    size_t i;
+    for(i = 0; i < numpages; ++i)
+    {
+        if(m_pages.Item(i).page == page)
+        {
+            return SetActivePage(i);
+        }
+    }
+    return false;
+}
+
+int wxRibbonBar::GetActivePage() const
+{
+    return m_current_page;
+}
+
+void wxRibbonBar::SetTabCtrlMargins(int left, int right)
+{
+    m_tab_margin_left = left;
+    m_tab_margin_right = right;
+
+    RecalculateTabSizes();
+}
+
+static int OrderPageTabInfoBySmallWidthAsc(wxRibbonPageTabInfo **first, wxRibbonPageTabInfo **second)
+{
+    return (**first).small_must_have_separator_width - (**second).small_must_have_separator_width;
+}
+
+void wxRibbonBar::RecalculateTabSizes()
+{
+    size_t numtabs = m_pages.GetCount();
+
+    if(numtabs == 0)
+        return;
+
+    int width = GetSize().GetWidth() - m_tab_margin_left - m_tab_margin_right;
+    int tabsep = m_art->GetMetric(wxRIBBON_ART_TAB_SEPARATION_SIZE);
+    int x = m_tab_margin_left;
+    const int y = 0;
+
+    if(width >= m_tabs_total_width_ideal)
+    {
+        // Simple case: everything at ideal width
+        size_t i;
+        for(i = 0; i < numtabs; ++i)
+        {
+            wxRibbonPageTabInfo& info = m_pages.Item(i);
+            info.rect.x = x;
+            info.rect.y = y;
+            info.rect.width = info.ideal_width;
+            info.rect.height = m_tab_height;
+            x += info.rect.width + tabsep;
+        }
+        m_tab_scroll_buttons_shown = false;
+        m_tab_scroll_left_button_rect.SetWidth(0);
+        m_tab_scroll_right_button_rect.SetWidth(0);
+    }
+    else if(width < m_tabs_total_width_minimum)
+    {
+        // Simple case: everything minimum with scrollbar
+        size_t i;
+        for(i = 0; i < numtabs; ++i)
+        {
+            wxRibbonPageTabInfo& info = m_pages.Item(i);
+            info.rect.x = x;
+            info.rect.y = y;
+            info.rect.width = info.minimum_width;
+            info.rect.height = m_tab_height;
+            x += info.rect.width + tabsep;
+        }
+        if(!m_tab_scroll_buttons_shown)
+        {
+            m_tab_scroll_left_button_state = wxRIBBON_SCROLL_BTN_NORMAL;
+            m_tab_scroll_right_button_state = wxRIBBON_SCROLL_BTN_NORMAL;
+            m_tab_scroll_buttons_shown = true;
+        }
+        {
+            wxMemoryDC temp_dc;
+            m_tab_scroll_left_button_rect.SetWidth(m_art->GetScrollButtonMinimumSize(temp_dc, this, wxRIBBON_SCROLL_BTN_LEFT | wxRIBBON_SCROLL_BTN_NORMAL | wxRIBBON_SCROLL_BTN_FOR_TABS).GetWidth());
+            m_tab_scroll_left_button_rect.SetHeight(m_tab_height);
+            m_tab_scroll_left_button_rect.SetX(m_tab_margin_left);
+            m_tab_scroll_left_button_rect.SetY(0);
+            m_tab_scroll_right_button_rect.SetWidth(m_art->GetScrollButtonMinimumSize(temp_dc, this, wxRIBBON_SCROLL_BTN_RIGHT | wxRIBBON_SCROLL_BTN_NORMAL | wxRIBBON_SCROLL_BTN_FOR_TABS).GetWidth());
+            m_tab_scroll_right_button_rect.SetHeight(m_tab_height);
+            m_tab_scroll_right_button_rect.SetX(GetClientSize().GetWidth() - m_tab_margin_right - m_tab_scroll_right_button_rect.GetWidth());
+            m_tab_scroll_right_button_rect.SetY(0);
+        }
+        if(m_tab_scroll_amount == 0)
+        {
+            m_tab_scroll_left_button_rect.SetWidth(0);
+        }
+        else if(m_tab_scroll_amount + width >= m_tabs_total_width_minimum)
+        {
+            m_tab_scroll_amount = m_tabs_total_width_minimum - width;
+            m_tab_scroll_right_button_rect.SetX(m_tab_scroll_right_button_rect.GetX() + m_tab_scroll_right_button_rect.GetWidth());
+            m_tab_scroll_right_button_rect.SetWidth(0);
+        }
+        for(i = 0; i < numtabs; ++i)
+        {
+            wxRibbonPageTabInfo& info = m_pages.Item(i);
+            info.rect.x -= m_tab_scroll_amount;
+        }
+    }
+    else
+    {
+        m_tab_scroll_buttons_shown = false;
+        m_tab_scroll_left_button_rect.SetWidth(0);
+        m_tab_scroll_right_button_rect.SetWidth(0);
+        // Complex case: everything sized such that: minimum <= width < ideal
+        /*
+           Strategy:
+             1) Uniformly reduce all tab widths from ideal to small_must_have_separator_width
+             2) Reduce the largest tab by 1 pixel, repeating until all tabs are same width (or at minimum)
+             3) Uniformly reduce all tabs down to their minimum width
+        */
+        int smallest_tab_width = INT_MAX;
+        int total_small_width = tabsep * (numtabs - 1);
+        size_t i;
+        for(i = 0; i < numtabs; ++i)
+        {
+            wxRibbonPageTabInfo& info = m_pages.Item(i);
+            if(info.small_must_have_separator_width < smallest_tab_width)
+            {
+                smallest_tab_width = info.small_must_have_separator_width;
+            }
+            total_small_width += info.small_must_have_separator_width;
+        }
+        if(width >= total_small_width)
+        {
+            // Do (1)
+            int total_delta = m_tabs_total_width_ideal - total_small_width;
+            total_small_width -= tabsep * (numtabs - 1);
+            width -= tabsep * (numtabs - 1);
+            for(i = 0; i < numtabs; ++i)
+            {
+                wxRibbonPageTabInfo& info = m_pages.Item(i);
+                int delta = info.ideal_width - info.small_must_have_separator_width;
+                info.rect.x = x;
+                info.rect.y = y;
+                info.rect.width = info.small_must_have_separator_width + delta * (width - total_small_width) / total_delta;
+                info.rect.height = m_tab_height;
+
+                x += info.rect.width + tabsep;
+                total_delta -= delta;
+                total_small_width -= info.small_must_have_separator_width;
+                width -= info.rect.width;
+            }
+        }
+        else
+        {
+            total_small_width = tabsep * (numtabs - 1);
+            for(i = 0; i < numtabs; ++i)
+            {
+                wxRibbonPageTabInfo& info = m_pages.Item(i);
+                if(info.minimum_width < smallest_tab_width)
+                {
+                    total_small_width += smallest_tab_width;
+                }
+                else
+                {
+                    total_small_width += info.minimum_width;
+                }
+            }
+            if(width >= total_small_width)
+            {
+                // Do (2)
+                wxRibbonPageTabInfoArray sorted_pages;
+                for(i = 0; i < numtabs; ++i)
+                {
+                    // Sneaky obj array trickery to not copy the tab descriptors
+                    sorted_pages.Add(&m_pages.Item(i));
+                }
+                sorted_pages.Sort(OrderPageTabInfoBySmallWidthAsc);
+                width -= tabsep * (numtabs - 1);
+                for(i = 0; i < numtabs; ++i)
+                {
+                    wxRibbonPageTabInfo& info = sorted_pages.Item(i);
+                    if(info.small_must_have_separator_width * (int)(numtabs - i) <= width)
+                    {
+                        info.rect.width = info.small_must_have_separator_width;;
+                    }
+                    else
+                    {
+                        info.rect.width = width / (numtabs - i);
+                    }
+                    width -= info.rect.width;
+                }
+                for(i = 0; i < numtabs; ++i)
+                {
+                    wxRibbonPageTabInfo& info = m_pages.Item(i);
+                    info.rect.x = x;
+                    info.rect.y = y;
+                    info.rect.height = m_tab_height;
+                    x += info.rect.width + tabsep;
+                    sorted_pages.Detach(numtabs - (i + 1));
+                }
+            }
+            else
+            {
+                // Do (3)
+                total_small_width = (smallest_tab_width + tabsep) * numtabs - tabsep;
+                int total_delta = total_small_width - m_tabs_total_width_minimum;
+                total_small_width = m_tabs_total_width_minimum - tabsep * (numtabs - 1);
+                width -= tabsep * (numtabs - 1);
+                for(i = 0; i < numtabs; ++i)
+                {
+                    wxRibbonPageTabInfo& info = m_pages.Item(i);
+                    int delta = smallest_tab_width - info.minimum_width;
+                    info.rect.x = x;
+                    info.rect.y = y;
+                    info.rect.width = info.minimum_width + delta * (width - total_small_width) / total_delta;
+                    info.rect.height = m_tab_height;
+
+                    x += info.rect.width + tabsep;
+                    total_delta -= delta;
+                    total_small_width -= info.minimum_width;
+                    width -= info.rect.width;
+                }
+            }
+        }
+    }
+}
+
+wxRibbonBar::wxRibbonBar()
+{
+    m_flags = 0;
+    m_tabs_total_width_ideal = 0;
+    m_tabs_total_width_minimum = 0;
+    m_tab_margin_left = 0;
+    m_tab_margin_right = 0;
+    m_tab_height = 0;
+    m_tab_scroll_amount = 0;
+    m_current_page = -1;
+    m_current_hovered_page = -1;
+    m_tab_scroll_left_button_state = wxRIBBON_SCROLL_BTN_NORMAL;
+    m_tab_scroll_right_button_state = wxRIBBON_SCROLL_BTN_NORMAL;
+    m_tab_scroll_buttons_shown = false;
+}
+
+wxRibbonBar::wxRibbonBar(wxWindow* parent,
+                         wxWindowID id,
+                         const wxPoint& pos,
+                         const wxSize& size,
+                         long style)
+    : wxRibbonControl(parent, id, pos, size, wxBORDER_NONE)
+{
+    CommonInit(style);
+}
+
+wxRibbonBar::~wxRibbonBar()
+{
+    SetArtProvider(NULL);
+}
+
+bool wxRibbonBar::Create(wxWindow* parent,
+                wxWindowID id,
+                const wxPoint& pos,
+                const wxSize& size,
+                long style)
+{
+    if(!wxRibbonControl::Create(parent, id, pos, size, wxBORDER_NONE))
+        return false;
+
+    CommonInit(style);
+
+    return true;
+}
+
+void wxRibbonBar::CommonInit(long style)
+{
+    SetName(wxT("wxRibbonBar"));
+
+    m_flags = style;
+    m_tabs_total_width_ideal = 0;
+    m_tabs_total_width_minimum = 0;
+    m_tab_margin_left = 50;
+    m_tab_margin_right = 20;
+    m_tab_height = 20; // initial guess
+    m_tab_scroll_amount = 0;
+    m_current_page = -1;
+    m_current_hovered_page = -1;
+    m_tab_scroll_left_button_state = wxRIBBON_SCROLL_BTN_NORMAL;
+    m_tab_scroll_right_button_state = wxRIBBON_SCROLL_BTN_NORMAL;
+    m_tab_scroll_buttons_shown = false;
+
+    if(m_art == NULL)
+    {
+        SetArtProvider(new wxRibbonDefaultArtProvider);
+    }
+    SetBackgroundStyle(wxBG_STYLE_CUSTOM);
+}
+
+void wxRibbonBar::SetArtProvider(wxRibbonArtProvider* art)
+{
+    wxRibbonArtProvider *old = m_art;
+    m_art = art;
+
+    if(art)
+    {
+        art->SetFlags(m_flags);
+    }
+    size_t numpages = m_pages.GetCount();
+    size_t i;
+    for(i = 0; i < numpages; ++i)
+    {
+        wxRibbonPage *page = m_pages.Item(i).page;
+        if(page->GetArtProvider() != art)
+        {
+            page->SetArtProvider(art);
+        }
+    }
+
+    delete old;
+}
+
+void wxRibbonBar::OnPaint(wxPaintEvent& WXUNUSED(evt))
+{
+    wxAutoBufferedPaintDC dc(this);
+
+    if(GetUpdateRegion().Contains(0, 0, GetClientSize().GetWidth(), m_tab_height) == wxOutRegion)
+    {
+        // Nothing to do in the tab area, and the page area is handled by the active page
+        return;
+    }
+
+    DoEraseBackground(dc);
+
+    size_t numtabs = m_pages.GetCount();
+    double sep_visibility = 0.0;
+    bool draw_sep = false;
+    wxRect tabs_rect(m_tab_margin_left, 0, GetClientSize().GetWidth() - m_tab_margin_left - m_tab_margin_right, m_tab_height);
+    if(m_tab_scroll_buttons_shown)
+    {
+        tabs_rect.x += m_tab_scroll_left_button_rect.GetWidth();
+        tabs_rect.width -= m_tab_scroll_left_button_rect.GetWidth() + m_tab_scroll_right_button_rect.GetWidth();
+    }
+    size_t i;
+    for(i = 0; i < numtabs; ++i)
+    {
+        wxRibbonPageTabInfo& info = m_pages.Item(i);
+
+        dc.DestroyClippingRegion();
+        if(m_tab_scroll_buttons_shown)
+        {
+            if(!tabs_rect.Intersects(info.rect))
+                continue;
+            dc.SetClippingRegion(tabs_rect);
+        }
+        dc.SetClippingRegion(info.rect);
+        m_art->DrawTab(dc, this, info);
+
+        if(info.rect.width < info.small_begin_need_separator_width)
+        {
+            draw_sep = true;
+            if(info.rect.width < info.small_must_have_separator_width)
+            {
+                sep_visibility += 1.0;
+            }
+            else
+            {
+                sep_visibility += (double)(info.small_begin_need_separator_width - info.rect.width) / (double)(info.small_begin_need_separator_width - info.small_must_have_separator_width);
+            }
+        }
+    }
+    if(draw_sep)
+    {
+        wxRect rect = m_pages.Item(0).rect;
+        rect.width = m_art->GetMetric(wxRIBBON_ART_TAB_SEPARATION_SIZE);
+        sep_visibility /= (double)numtabs;
+        for(i = 0; i < numtabs - 1; ++i)
+        {
+            wxRibbonPageTabInfo& info = m_pages.Item(i);
+            rect.x = info.rect.x + info.rect.width;
+
+            if(m_tab_scroll_buttons_shown && !tabs_rect.Intersects(rect))
+            {
+                continue;
+            }
+
+            dc.DestroyClippingRegion();
+            dc.SetClippingRegion(rect);
+            m_art->DrawTabSeparator(dc, this, rect, sep_visibility);
+        }
+    }
+    if(m_tab_scroll_buttons_shown)
+    {
+        dc.DestroyClippingRegion();
+        if(m_tab_scroll_left_button_rect.GetWidth() != 0)
+        {
+            m_art->DrawScrollButton(dc, this, m_tab_scroll_left_button_rect, wxRIBBON_SCROLL_BTN_LEFT | m_tab_scroll_left_button_state | wxRIBBON_SCROLL_BTN_FOR_TABS);
+        }
+        if(m_tab_scroll_right_button_rect.GetWidth() != 0)
+        {
+            m_art->DrawScrollButton(dc, this, m_tab_scroll_right_button_rect, wxRIBBON_SCROLL_BTN_RIGHT | m_tab_scroll_right_button_state | wxRIBBON_SCROLL_BTN_FOR_TABS);
+        }
+    }
+}
+
+void wxRibbonBar::OnEraseBackground(wxEraseEvent& WXUNUSED(evt))
+{
+    // Background painting done in main paint handler to reduce screen flicker
+}
+
+void wxRibbonBar::DoEraseBackground(wxDC& dc)
+{
+    wxRect tabs(GetSize());
+    tabs.height = m_tab_height;
+    m_art->DrawTabCtrlBackground(dc, this, tabs);
+}
+
+void wxRibbonBar::OnSize(wxSizeEvent& evt)
+{
+    RecalculateTabSizes();
+    if(m_current_page != -1)
+    {
+        RepositionPage(m_pages.Item(m_current_page).page);
+    }
+    RefreshTabBar();
+
+    evt.Skip();
+}
+
+void wxRibbonBar::RepositionPage(wxRibbonPage *page)
+{
+    int w, h;
+    GetSize(&w, &h);
+    page->SetSizeWithScrollButtonAdjustment(0, m_tab_height, w, h - m_tab_height);
+}
+
+wxRibbonPageTabInfo* wxRibbonBar::HitTestTabs(wxPoint position, int* index)
+{
+    wxRect tabs_rect(m_tab_margin_left, 0, GetClientSize().GetWidth() - m_tab_margin_left - m_tab_margin_right, m_tab_height);
+    if(m_tab_scroll_buttons_shown)
+    {
+        tabs_rect.SetX(tabs_rect.GetX() + m_tab_scroll_left_button_rect.GetWidth());
+        tabs_rect.SetWidth(tabs_rect.GetWidth() - m_tab_scroll_left_button_rect.GetWidth() - m_tab_scroll_right_button_rect.GetWidth());
+    }
+    if(tabs_rect.Contains(position))
+    {
+        size_t numtabs = m_pages.GetCount();
+        size_t i;
+        for(i = 0; i < numtabs; ++i)
+        {
+            wxRibbonPageTabInfo& info = m_pages.Item(i);
+            if(info.rect.Contains(position))
+            {
+                if(index != NULL)
+                {
+                    *index = (int)i;
+                }
+                return &info;
+            }
+        }
+    }
+    if(index != NULL)
+    {
+        *index = -1;
+    }
+    return NULL;
+}
+
+void wxRibbonBar::OnMouseLeftDown(wxMouseEvent& evt)
+{
+    wxRibbonPageTabInfo *tab = HitTestTabs(evt.GetPosition());
+    if(tab && tab != &m_pages.Item(m_current_page))
+    {
+        wxRibbonBarEvent query(wxEVT_COMMAND_RIBBONBAR_PAGE_CHANGING, GetId(), tab->page);
+        query.SetEventObject(this);
+        ProcessWindowEvent(query);
+        if(query.IsAllowed())
+        {
+            SetActivePage(query.GetPage());
+
+            wxRibbonBarEvent notification(wxEVT_COMMAND_RIBBONBAR_PAGE_CHANGED, GetId(), m_pages.Item(m_current_page).page);
+            notification.SetEventObject(this);
+            ProcessWindowEvent(notification);
+        }
+    }
+    else if(tab == NULL)
+    {
+        if(m_tab_scroll_left_button_rect.Contains(evt.GetPosition()))
+        {
+            m_tab_scroll_left_button_state |= wxRIBBON_SCROLL_BTN_ACTIVE | wxRIBBON_SCROLL_BTN_HOVERED;
+            RefreshTabBar();
+        }
+        else if(m_tab_scroll_right_button_rect.Contains(evt.GetPosition()))
+        {
+            m_tab_scroll_right_button_state |= wxRIBBON_SCROLL_BTN_ACTIVE | wxRIBBON_SCROLL_BTN_HOVERED;
+            RefreshTabBar();
+        }
+    }
+}
+
+void wxRibbonBar::OnMouseLeftUp(wxMouseEvent& WXUNUSED(evt))
+{
+    if(!m_tab_scroll_buttons_shown)
+    {
+        return;
+    }
+
+    int amount = 0;
+    if(m_tab_scroll_left_button_state & wxRIBBON_SCROLL_BTN_ACTIVE)
+    {
+        amount = -1;
+    }
+    else if(m_tab_scroll_right_button_state & wxRIBBON_SCROLL_BTN_ACTIVE)
+    {
+        amount = 1;
+    }
+    if(amount != 0)
+    {
+        m_tab_scroll_left_button_state &= ~wxRIBBON_SCROLL_BTN_ACTIVE;
+        m_tab_scroll_right_button_state &= ~wxRIBBON_SCROLL_BTN_ACTIVE;
+        ScrollTabBar(amount * 8);
+    }
+}
+
+void wxRibbonBar::ScrollTabBar(int amount)
+{
+    bool show_left = true;
+    bool show_right = true;
+    if(m_tab_scroll_amount + amount <= 0)
+    {
+        amount = -m_tab_scroll_amount;
+        show_left = false;
+    }
+    else if(m_tab_scroll_amount + amount + (GetClientSize().GetWidth() - m_tab_margin_left - m_tab_margin_right) >= m_tabs_total_width_minimum)
+    {
+        amount = m_tabs_total_width_minimum - m_tab_scroll_amount - (GetClientSize().GetWidth() - m_tab_margin_left - m_tab_margin_right);
+        show_right = false;
+    }
+    if(amount == 0)
+    {
+        return;
+    }
+    m_tab_scroll_amount += amount;
+    size_t numtabs = m_pages.GetCount();
+    size_t i;
+    for(i = 0; i < numtabs; ++i)
+    {
+        wxRibbonPageTabInfo& info = m_pages.Item(i);
+        info.rect.SetX(info.rect.GetX() - amount);
+    }
+    if(show_right != (m_tab_scroll_right_button_rect.GetWidth() != 0) ||
+        show_left != (m_tab_scroll_left_button_rect.GetWidth() != 0))
+    {
+        wxMemoryDC temp_dc;
+        if(show_left)
+        {
+            m_tab_scroll_left_button_rect.SetWidth(m_art->GetScrollButtonMinimumSize(temp_dc, this, wxRIBBON_SCROLL_BTN_LEFT | wxRIBBON_SCROLL_BTN_NORMAL | wxRIBBON_SCROLL_BTN_FOR_TABS).GetWidth());
+        }
+        else
+        {
+            m_tab_scroll_left_button_rect.SetWidth(0);
+        }
+
+        if(show_right)
+        {
+            if(m_tab_scroll_right_button_rect.GetWidth() == 0)
+            {
+                m_tab_scroll_right_button_rect.SetWidth(m_art->GetScrollButtonMinimumSize(temp_dc, this, wxRIBBON_SCROLL_BTN_RIGHT | wxRIBBON_SCROLL_BTN_NORMAL | wxRIBBON_SCROLL_BTN_FOR_TABS).GetWidth());
+                m_tab_scroll_right_button_rect.SetX(m_tab_scroll_right_button_rect.GetX() - m_tab_scroll_right_button_rect.GetWidth());
+            }
+        }
+        else
+        {
+            if(m_tab_scroll_right_button_rect.GetWidth() != 0)
+            {
+                m_tab_scroll_right_button_rect.SetX(m_tab_scroll_right_button_rect.GetX() + m_tab_scroll_right_button_rect.GetWidth());
+                m_tab_scroll_right_button_rect.SetWidth(0);
+            }
+        }
+    }
+
+    RefreshTabBar();
+}
+
+void wxRibbonBar::RefreshTabBar()
+{
+    wxRect tab_rect(0, 0, GetClientSize().GetWidth(), m_tab_height);
+    Refresh(false, &tab_rect);
+}
+
+void wxRibbonBar::OnMouseMiddleDown(wxMouseEvent& evt)
+{
+    DoMouseButtonCommon(evt, wxEVT_COMMAND_RIBBONBAR_TAB_MIDDLE_DOWN);
+}
+
+void wxRibbonBar::OnMouseMiddleUp(wxMouseEvent& evt)
+{
+    DoMouseButtonCommon(evt, wxEVT_COMMAND_RIBBONBAR_TAB_MIDDLE_UP);
+}
+
+void wxRibbonBar::OnMouseRightDown(wxMouseEvent& evt)
+{
+    DoMouseButtonCommon(evt, wxEVT_COMMAND_RIBBONBAR_TAB_RIGHT_DOWN);
+}
+
+void wxRibbonBar::OnMouseRightUp(wxMouseEvent& evt)
+{
+    DoMouseButtonCommon(evt, wxEVT_COMMAND_RIBBONBAR_TAB_RIGHT_UP);
+}
+
+void wxRibbonBar::DoMouseButtonCommon(wxMouseEvent& evt, wxEventType tab_event_type)
+{
+    wxRibbonPageTabInfo *tab = HitTestTabs(evt.GetPosition());
+    if(tab)
+    {
+        wxRibbonBarEvent notification(tab_event_type, GetId(), tab->page);
+        notification.SetEventObject(this);
+        ProcessWindowEvent(notification);
+    }
+}
+
+void wxRibbonBar::RecalculateMinSize()
+{
+    wxSize min_size(wxDefaultCoord, wxDefaultCoord);
+    size_t numtabs = m_pages.GetCount();
+    if(numtabs != 0)
+    {
+        min_size = m_pages.Item(0).page->GetMinSize();
+
+        size_t i;
+        for(i = 1; i < numtabs; ++i)
+        {
+            wxRibbonPageTabInfo& info = m_pages.Item(i);
+            wxSize page_min = info.page->GetMinSize();
+
+            min_size.x = wxMax(min_size.x, page_min.x);
+            min_size.y = wxMax(min_size.y, page_min.y);
+        }
+    }
+    if(min_size.y != wxDefaultCoord)
+    {
+        // TODO: Decide on best course of action when min height is unspecified
+        // - should we specify it to the tab minimum, or leave it unspecified?
+        min_size.IncBy(0, m_tab_height);
+    }
+
+    m_minWidth = min_size.GetWidth();
+    m_minHeight = min_size.GetHeight();
+}
+
+wxSize wxRibbonBar::DoGetBestSize() const
+{
+    wxSize best(0, 0);
+    if(m_current_page != -1)
+    {
+        best = m_pages.Item(m_current_page).page->GetBestSize();
+    }
+    if(best.GetHeight() == wxDefaultCoord)
+    {
+        best.SetHeight(m_tab_height);
+    }
+    else
+    {
+        best.IncBy(0, m_tab_height);
+    }
+    return best;
+}
+
+#endif // wxUSE_RIBBON