///////////////////////////////////////////////////////////////////////////////
// Name:        src/ribbon/page.cpp
// Purpose:     Container for ribbon-bar-style interface panels
// Author:      Peter Cawley
// Created:     2009-05-25
// Copyright:   (C) Peter Cawley
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#include "wx/wxprec.h"


#if wxUSE_RIBBON

#include "wx/ribbon/page.h"
#include "wx/ribbon/art.h"
#include "wx/ribbon/bar.h"
#include "wx/dcbuffer.h"

#ifndef WX_PRECOMP
#endif

#ifdef __WXMSW__
#include "wx/msw/private.h"
#endif

static int GetSizeInOrientation(wxSize size, wxOrientation orientation);

// As scroll buttons need to be rendered on top of a page's child windows, the
// buttons themselves have to be proper child windows (rather than just painted
// onto the page). In order to get proper clipping of a page's children (with
// regard to the scroll button), the scroll buttons are created as children of
// the ribbon bar rather than children of the page. This could not have been
// achieved by creating buttons as children of the page and then doing some Z-order
// manipulation, as this causes problems on win32 due to ribbon panels having the
// transparent flag set.
class wxRibbonPageScrollButton : public wxRibbonControl
{
public:
    explicit wxRibbonPageScrollButton(wxRibbonPage* sibling,
                 wxWindowID id = wxID_ANY,
                 const wxPoint& pos = wxDefaultPosition,
                 const wxSize& size = wxDefaultSize,
                 long style = 0);

    virtual ~wxRibbonPageScrollButton();

protected:
    virtual wxBorder GetDefaultBorder() const override { return wxBORDER_NONE; }

    void OnEraseBackground(wxEraseEvent& evt);
    void OnPaint(wxPaintEvent& evt);
    void OnMouseEnter(wxMouseEvent& evt);
    void OnMouseLeave(wxMouseEvent& evt);
    void OnMouseDown(wxMouseEvent& evt);
    void OnMouseUp(wxMouseEvent& evt);

    wxRibbonPage* m_sibling = nullptr;
    long m_flags;

    wxDECLARE_CLASS(wxRibbonPageScrollButton);
    wxDECLARE_EVENT_TABLE();
};

wxIMPLEMENT_CLASS(wxRibbonPageScrollButton, wxRibbonControl);

wxBEGIN_EVENT_TABLE(wxRibbonPageScrollButton, wxRibbonControl)
    EVT_ENTER_WINDOW(wxRibbonPageScrollButton::OnMouseEnter)
    EVT_ERASE_BACKGROUND(wxRibbonPageScrollButton::OnEraseBackground)
    EVT_LEAVE_WINDOW(wxRibbonPageScrollButton::OnMouseLeave)
    EVT_LEFT_DOWN(wxRibbonPageScrollButton::OnMouseDown)
    EVT_LEFT_UP(wxRibbonPageScrollButton::OnMouseUp)
    EVT_PAINT(wxRibbonPageScrollButton::OnPaint)
wxEND_EVENT_TABLE()

wxRibbonPageScrollButton::wxRibbonPageScrollButton(wxRibbonPage* sibling,
                 wxWindowID id,
                 const wxPoint& pos,
                 const wxSize& size,
                 long style) : wxRibbonControl(sibling->GetParent(), id, pos, size, wxBORDER_NONE)
{
    SetBackgroundStyle(wxBG_STYLE_PAINT);
    m_sibling = sibling;
    m_flags = (style & wxRIBBON_SCROLL_BTN_DIRECTION_MASK) | wxRIBBON_SCROLL_BTN_FOR_PAGE;
}

wxRibbonPageScrollButton::~wxRibbonPageScrollButton()
{
}

void wxRibbonPageScrollButton::OnEraseBackground(wxEraseEvent& WXUNUSED(evt))
{
    // Do nothing - all painting done in main paint handler
}

void wxRibbonPageScrollButton::OnPaint(wxPaintEvent& WXUNUSED(evt))
{
    wxAutoBufferedPaintDC dc(this);
    if(m_art)
    {
        m_art->DrawScrollButton(dc, this, GetSize(), m_flags);
    }
}

void wxRibbonPageScrollButton::OnMouseEnter(wxMouseEvent& WXUNUSED(evt))
{
    m_flags |= wxRIBBON_SCROLL_BTN_HOVERED;
    Refresh(false);
}

void wxRibbonPageScrollButton::OnMouseLeave(wxMouseEvent& WXUNUSED(evt))
{
    m_flags &= ~wxRIBBON_SCROLL_BTN_HOVERED;
    m_flags &= ~wxRIBBON_SCROLL_BTN_ACTIVE;
    Refresh(false);
}

void wxRibbonPageScrollButton::OnMouseDown(wxMouseEvent& WXUNUSED(evt))
{
    m_flags |= wxRIBBON_SCROLL_BTN_ACTIVE;
    Refresh(false);
}

void wxRibbonPageScrollButton::OnMouseUp(wxMouseEvent& WXUNUSED(evt))
{
    if(m_flags & wxRIBBON_SCROLL_BTN_ACTIVE)
    {
        m_flags &= ~wxRIBBON_SCROLL_BTN_ACTIVE;
        Refresh(false);
        switch(m_flags & wxRIBBON_SCROLL_BTN_DIRECTION_MASK)
        {
        case wxRIBBON_SCROLL_BTN_DOWN:
        case wxRIBBON_SCROLL_BTN_RIGHT:
            m_sibling->ScrollSections(1);
            break;
        case wxRIBBON_SCROLL_BTN_UP:
        case wxRIBBON_SCROLL_BTN_LEFT:
            m_sibling->ScrollSections(-1);
            break;
        default:
            break;
        }
    }
}

wxIMPLEMENT_CLASS(wxRibbonPage, wxRibbonControl);

wxBEGIN_EVENT_TABLE(wxRibbonPage, wxRibbonControl)
    EVT_ERASE_BACKGROUND(wxRibbonPage::OnEraseBackground)
    EVT_PAINT(wxRibbonPage::OnPaint)
    EVT_SIZE(wxRibbonPage::OnSize)
    EVT_DPI_CHANGED(wxRibbonPage::OnDPIChanged)
    EVT_SYS_COLOUR_CHANGED(wxRibbonPage::OnSysColourChanged)
wxEND_EVENT_TABLE()

wxRibbonPage::wxRibbonPage()
{
    m_scroll_left_btn = nullptr;
    m_scroll_right_btn = nullptr;
    m_scroll_amount = 0;
    m_scroll_buttons_visible = false;
}

wxRibbonPage::wxRibbonPage(wxRibbonBar* parent,
                   wxWindowID id,
                   const wxString& label,
                   const wxBitmapBundle& icon,
                   long WXUNUSED(style))
    : wxRibbonControl(parent, id, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE)
{
    CommonInit(label, icon);
}

wxRibbonPage::~wxRibbonPage()
{
    delete[] m_size_calc_array;
    delete m_scroll_left_btn;
    delete m_scroll_right_btn;
}

bool wxRibbonPage::Create(wxRibbonBar* parent,
                wxWindowID id,
                const wxString& label,
                const wxBitmapBundle& icon,
                long WXUNUSED(style))
{
    if(!wxRibbonControl::Create(parent, id, wxDefaultPosition, wxDefaultSize, wxBORDER_NONE))
        return false;

    CommonInit(label, icon);

    return true;
}

void wxRibbonPage::CommonInit(const wxString& label, const wxBitmapBundle& icon)
{
    SetName(label);

    SetLabel(label);
    m_icon = icon;
    m_scroll_left_btn = nullptr;
    m_scroll_right_btn = nullptr;
    m_size_calc_array = nullptr;
    m_size_calc_array_size = 0;
    m_scroll_amount = 0;
    m_scroll_buttons_visible = false;

    SetBackgroundStyle(wxBG_STYLE_PAINT);

    if (auto* const bar = wxCheckedStaticCast<wxRibbonBar>(GetParent()))
        bar->AddPage(this);
}

void wxRibbonPage::SetArtProvider(wxRibbonArtProvider* art)
{
    m_art = art;
    for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
          node;
          node = node->GetNext() )
    {
        wxWindow* child = node->GetData();
        wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
        if(ribbon_child)
        {
            ribbon_child->SetArtProvider(art);
        }
    }

    // The scroll buttons are children of the parent ribbon control, not the
    // page, so they're not taken into account by the loop above, but they
    // still use the same art provider, so we need to update them too.
    if ( m_scroll_left_btn )
        m_scroll_left_btn->SetArtProvider(art);
    if ( m_scroll_right_btn )
        m_scroll_right_btn->SetArtProvider(art);
}

void wxRibbonPage::AdjustRectToIncludeScrollButtons(wxRect* rect) const
{
    if(m_scroll_buttons_visible)
    {
        if(GetMajorAxis() == wxVERTICAL)
        {
            if(m_scroll_left_btn)
            {
                rect->SetY(rect->GetY() -
                    m_scroll_left_btn->GetSize().GetHeight());
                rect->SetHeight(rect->GetHeight() +
                    m_scroll_left_btn->GetSize().GetHeight());
            }
            if(m_scroll_right_btn)
            {
                rect->SetHeight(rect->GetHeight() +
                    m_scroll_right_btn->GetSize().GetHeight());
            }
        }
        else
        {
            if(m_scroll_left_btn)
            {
                rect->SetX(rect->GetX() -
                    m_scroll_left_btn->GetSize().GetWidth());
                rect->SetWidth(rect->GetWidth() +
                    m_scroll_left_btn->GetSize().GetWidth());
            }
            if(m_scroll_right_btn)
            {
                rect->SetWidth(rect->GetWidth() +
                    m_scroll_right_btn->GetSize().GetWidth());
            }
        }
    }
}

void wxRibbonPage::OnEraseBackground(wxEraseEvent& WXUNUSED(evt))
{
    // All painting done in main paint handler to minimise flicker
}

void wxRibbonPage::OnPaint(wxPaintEvent& WXUNUSED(evt))
{
    // No foreground painting done by the page itself, but a paint DC
    // must be created anyway.
    wxAutoBufferedPaintDC dc(this);
    wxRect rect(GetSize());
    AdjustRectToIncludeScrollButtons(&rect);
    m_art->DrawPageBackground(dc, this, rect);
}

wxOrientation wxRibbonPage::GetMajorAxis() const
{
    if(m_art && (m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL))
    {
        return wxVERTICAL;
    }
    else
    {
        return wxHORIZONTAL;
    }
}

bool wxRibbonPage::ScrollLines(int lines)
{
    return ScrollPixels(lines * 8);
}

bool wxRibbonPage::ScrollPixels(int pixels)
{
    if(pixels < 0)
    {
        if(m_scroll_amount == 0)
            return false;
        if(m_scroll_amount < -pixels)
            pixels = -m_scroll_amount;
    }
    else if(pixels > 0)
    {
        if(m_scroll_amount == m_scroll_amount_limit)
            return false;
        if(m_scroll_amount + pixels > m_scroll_amount_limit)
            pixels = m_scroll_amount_limit - m_scroll_amount;
    }
    else
        return false;

    m_scroll_amount += pixels;

    for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
              node;
              node = node->GetNext() )
    {
        wxWindow* child = node->GetData();
        int x, y;
        child->GetPosition(&x, &y);
        if(GetMajorAxis() == wxHORIZONTAL)
            x -= pixels;
        else
            y -= pixels;
        child->SetPosition(wxPoint(x, y));
    }

    if (ShowScrollButtons())
        DoActualLayout();
    Refresh();
    return true;
}

bool wxRibbonPage::ScrollSections(int sections)
{
    // Currently the only valid values are -1 and 1 for scrolling left and
    // right, respectively.
    const bool scrollForward = sections >= 1;

    // Determine by how many pixels to scroll. If something on the page
    // is partially visible, scroll to make it fully visible. Otherwise
    // find the next item that will become visible and scroll to make it
    // fully visible. The ScrollPixel call will correct if we scroll too
    // much if the available width is smaller than the items.

    // Scroll at minimum the same amount as ScrollLines(1):
    int minscroll = sections * 8;
    // How many pixels to scroll:
    int pixels = 0;

    // Determine the scroll position, that is, the page border where items
    // are appearing.
    int scrollpos = 0;

    wxOrientation major_axis = GetMajorAxis();
    int gap = 0;

    int width = 0;
    int height = 0;
    int x = 0;
    int y = 0;
    GetSize(&width, &height);
    GetPosition(&x, &y);
    if(major_axis == wxHORIZONTAL)
    {
        gap = m_art->GetMetric(wxRIBBON_ART_PANEL_X_SEPARATION_SIZE);
        if (scrollForward)
        {
            scrollpos = width - m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_RIGHT_SIZE);
        }
        else
        {
            scrollpos = m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_LEFT_SIZE);
        }
    }
    else
    {
        gap = m_art->GetMetric(wxRIBBON_ART_PANEL_Y_SEPARATION_SIZE);
        if (scrollForward)
        {
            scrollpos = width - m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_BOTTOM_SIZE);
        }
        else
        {
            scrollpos = m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_TOP_SIZE);
        }
    }

    // Find the child that is partially shown or just beyond the scroll position
    for(wxWindowList::compatibility_iterator
            node = scrollForward ? GetChildren().GetFirst()
                                 : GetChildren().GetLast();
        node;
        node = scrollForward ? node->GetNext()
                             : node->GetPrevious())
    {
        wxWindow* child = node->GetData();
        child->GetSize(&width, &height);
        child->GetPosition(&x, &y);
        int pos0 = 0;
        int pos1 = 0;
        if (major_axis == wxHORIZONTAL)
        {
            pos0 = x;
            pos1 = x + width + gap;
        }
        else
        {
            pos0 = y;
            pos1 = y + height + gap;
        }
        if (scrollpos >= pos0 && scrollpos <= pos1)
        {
            // This section is partially visible, scroll to make it fully visible.
            if (scrollForward)
            {
                pixels += pos1 - scrollpos;
            }
            else
            {
                pixels += pos0 - scrollpos;
            }
            if (abs(pixels) >= abs(minscroll))
                break;
        }
        if (scrollpos <= pos0 && scrollForward)
        {
            // This section is next, scroll the entire section width
            pixels += (pos1 - pos0);
            break;
        }
        if (scrollpos >= pos1 && !scrollForward)
        {
            // This section is next, scroll the entire section width
            pixels += (pos0 - pos1);
            break;
        }
    }
    // Do a final safety sanity check, should not be necessary, but will not hurt either.
    if (pixels == 0)
    {
        pixels = minscroll;
    }
    if (pixels * minscroll < 0)
    {
        pixels = -pixels;
    }

    return ScrollPixels(pixels);
}

void wxRibbonPage::SetSizeWithScrollButtonAdjustment(int x, int y, int width, int height)
{
    if(m_scroll_buttons_visible)
    {
        if(GetMajorAxis() == wxHORIZONTAL)
        {
            if(m_scroll_left_btn)
            {
                int w = m_scroll_left_btn->GetSize().GetWidth();
                m_scroll_left_btn->SetPosition(wxPoint(x, y));
                x += w;
                width -= w;
            }
            if(m_scroll_right_btn)
            {
                int w = m_scroll_right_btn->GetSize().GetWidth();
                width -= w;
                m_scroll_right_btn->SetPosition(wxPoint(x + width, y));
            }
        }
        else
        {
            if(m_scroll_left_btn)
            {
                int h = m_scroll_left_btn->GetSize().GetHeight();
                m_scroll_left_btn->SetPosition(wxPoint(x, y));
                y += h;
                height -= h;
            }
            if(m_scroll_right_btn)
            {
                int h = m_scroll_right_btn->GetSize().GetHeight();
                height -= h;
                m_scroll_right_btn->SetPosition(wxPoint(x, y + height));
            }
        }
    }
    if (width < 0) width = 0;
    if (height < 0) height = 0;
    SetSize(x, y, width, height);
}

void wxRibbonPage::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
    // When a resize triggers the scroll buttons to become visible, the page is resized.
    // This resize from within a resize event can cause (MSW) wxWidgets some confusion,
    // and report the 1st size to the 2nd size event. Hence the most recent size is
    // remembered internally and used in Layout() where appropriate.

    if(GetMajorAxis() == wxHORIZONTAL)
    {
        m_size_in_major_axis_for_children = width;
        if(m_scroll_buttons_visible)
        {
            if(m_scroll_left_btn)
                m_size_in_major_axis_for_children += m_scroll_left_btn->GetSize().GetWidth();
            if(m_scroll_right_btn)
                m_size_in_major_axis_for_children += m_scroll_right_btn->GetSize().GetWidth();
        }
    }
    else
    {
        m_size_in_major_axis_for_children = height;
        if(m_scroll_buttons_visible)
        {
            if(m_scroll_left_btn)
                m_size_in_major_axis_for_children += m_scroll_left_btn->GetSize().GetHeight();
            if(m_scroll_right_btn)
                m_size_in_major_axis_for_children += m_scroll_right_btn->GetSize().GetHeight();
        }
    }

    wxRibbonControl::DoSetSize(x, y, width, height, sizeFlags);
}

void wxRibbonPage::OnSize(wxSizeEvent& evt)
{
    wxSize new_size = evt.GetSize();

    if (m_art)
    {
        wxMemoryDC temp_dc;
        wxRect invalid_rect = m_art->GetPageBackgroundRedrawArea(temp_dc, this, m_old_size, new_size);
        Refresh(true, &invalid_rect);
    }

    m_old_size = new_size;

    if(new_size.GetX() > 0 && new_size.GetY() > 0)
    {
        Layout();
    }
    else
    {
        // Simplify other calculations by pretending new size is zero in both
        // X and Y
        new_size.Set(0, 0);
        // When size == 0, no point in doing any layout
    }

    evt.Skip();
}

void wxRibbonPage::OnDPIChanged(wxDPIChangedEvent& event)
{
    Realize();
    event.Skip();
}

void wxRibbonPage::OnSysColourChanged(wxSysColourChangedEvent& event)
{
    event.Skip();
    m_art->UpdateColoursFromSystem();
}

void wxRibbonPage::RemoveChild(wxWindowBase *child)
{
    // Remove all references to the child from the collapse stack
    size_t count = m_collapse_stack.GetCount();
    size_t src, dst;
    for(src = 0, dst = 0; src < count; ++src, ++dst)
    {
        wxRibbonControl *item = m_collapse_stack.Item(src);
        if(item == child)
        {
            ++src;
            if(src == count)
            {
                break;
            }
        }
        if(src != dst)
        {
            m_collapse_stack.Item(dst) = item;
        }
    }
    if(src > dst)
    {
        m_collapse_stack.RemoveAt(dst, src - dst);
    }

    // ... and then proceed as normal
    wxRibbonControl::RemoveChild(child);
}

bool wxRibbonPage::Realize()
{
    bool status = true;

    m_collapse_stack.Clear();
    for (wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
                  node;
                  node = node->GetNext())
    {
        wxRibbonControl* child = wxDynamicCast(node->GetData(), wxRibbonControl);
        if(child == nullptr)
        {
            continue;
        }
        if(!child->Realize())
        {
            status = false;
        }
    }
    PopulateSizeCalcArray(&wxWindow::GetMinSize);

    return DoActualLayout() && status;
}

void wxRibbonPage::PopulateSizeCalcArray(wxSize (wxWindow::*get_size)(void) const)
{
    wxSize parentSize = GetSize();
    parentSize.x -= m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_LEFT_SIZE);
    parentSize.x -= m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_RIGHT_SIZE);
    parentSize.y -= m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_TOP_SIZE);
    parentSize.y -= m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_BOTTOM_SIZE);

    if(m_size_calc_array_size != GetChildren().GetCount())
    {
        delete[] m_size_calc_array;
        m_size_calc_array_size = GetChildren().GetCount();
        m_size_calc_array = new wxSize[m_size_calc_array_size];
    }

    wxSize* node_size = m_size_calc_array;
    for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
          node;
          node = node->GetNext(), ++node_size )
    {
        wxWindow* child = node->GetData();
        wxRibbonPanel* panel = wxDynamicCast(child, wxRibbonPanel);
        if (panel && panel->GetFlags() & wxRIBBON_PANEL_FLEXIBLE)
            *node_size = panel->GetBestSizeForParentSize(parentSize);
        else
            *node_size = (child->*get_size)();
    }
}

bool wxRibbonPage::Layout()
{
    if(GetChildren().GetCount() == 0)
    {
        return true;
    }
    else
    {
        PopulateSizeCalcArray(&wxWindow::GetSize);
        return DoActualLayout();
    }
}

bool wxRibbonPage::DoActualLayout()
{
    wxPoint origin(m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_LEFT_SIZE), m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_TOP_SIZE));
    wxOrientation major_axis = GetMajorAxis();
    int gap;
    int minor_axis_size;
    int available_space;
    if(major_axis == wxHORIZONTAL)
    {
        gap = m_art->GetMetric(wxRIBBON_ART_PANEL_X_SEPARATION_SIZE);
        minor_axis_size = GetSize().GetHeight() - origin.y - m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_BOTTOM_SIZE);
        available_space = m_size_in_major_axis_for_children - m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_RIGHT_SIZE) - origin.x;
    }
    else
    {
        gap = m_art->GetMetric(wxRIBBON_ART_PANEL_Y_SEPARATION_SIZE);
        minor_axis_size = GetSize().GetWidth() - origin.x - m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_RIGHT_SIZE);
        available_space = m_size_in_major_axis_for_children - m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_BOTTOM_SIZE) - origin.y;
    }
    if (minor_axis_size < 0) minor_axis_size = 0;
    size_t size_index;
    for(size_index = 0; size_index < m_size_calc_array_size; ++size_index)
    {
        if(major_axis == wxHORIZONTAL)
        {
            available_space -= m_size_calc_array[size_index].GetWidth();
            m_size_calc_array[size_index].SetHeight(minor_axis_size);
        }
        else
        {
            available_space -= m_size_calc_array[size_index].GetHeight();
            m_size_calc_array[size_index].SetWidth(minor_axis_size);
        }
        if(size_index != 0)
            available_space -= gap;
    }
    bool todo_hide_scroll_buttons = false;
    bool todo_show_scroll_buttons = false;
    if(available_space >= 0)
    {
        if(m_scroll_buttons_visible)
            todo_hide_scroll_buttons = true;
        if(available_space > 0)
            ExpandPanels(major_axis, available_space);
    }
    else
    {
        if(m_scroll_buttons_visible)
        {
            // Scroll buttons already visible - not going to be able to downsize any more
            m_scroll_amount_limit = -available_space;
            if(m_scroll_amount > m_scroll_amount_limit)
            {
                m_scroll_amount = m_scroll_amount_limit;
                todo_show_scroll_buttons = true;
            }
        }
        else
        {
            if(!CollapsePanels(major_axis, -available_space))
            {
                m_scroll_amount = 0;
                m_scroll_amount_limit = -available_space;
                todo_show_scroll_buttons = true;
            }
        }
    }
    if(m_scroll_buttons_visible)
    {
        if(major_axis == wxHORIZONTAL)
        {
            origin.x -= m_scroll_amount;
            if(m_scroll_left_btn)
                origin.x -= m_scroll_left_btn->GetSize().GetWidth();
        }
        else
        {
            origin.y -= m_scroll_amount;
            if(m_scroll_left_btn)
                origin.y -= m_scroll_left_btn->GetSize().GetHeight();
        }
    }
    size_index = 0;
    for(wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
        node;
        node = node->GetNext(), ++size_index )
    {
        wxWindow* child = node->GetData();
        int w = m_size_calc_array[size_index].GetWidth();
        int h = m_size_calc_array[size_index].GetHeight();
        child->SetSize(origin.x, origin.y, w, h);
        if(major_axis == wxHORIZONTAL)
        {
            origin.x += w + (child->IsShown() ? gap : 0);
        }
        else
        {
            origin.y += h + (child->IsShown() ? gap : 0);
        }
    }

    if(todo_show_scroll_buttons)
        ShowScrollButtons();
    else if(todo_hide_scroll_buttons)
        HideScrollButtons();
    else if(m_scroll_buttons_visible)
        ShowScrollButtons();

    Refresh();
    return true;
}

bool wxRibbonPage::Show(bool show)
{
    if(m_scroll_left_btn)
        m_scroll_left_btn->Show(show);
    if(m_scroll_right_btn)
        m_scroll_right_btn->Show(show);
    return wxRibbonControl::Show(show);
}

void wxRibbonPage::HideScrollButtons()
{
    m_scroll_amount = 0;
    m_scroll_amount_limit = 0;
    ShowScrollButtons();
}

bool wxRibbonPage::ShowScrollButtons()
{
    bool show_left = true;
    bool show_right = true;
    bool reposition = false;
    if(m_scroll_amount == 0)
    {
        show_left = false;
    }
    if(m_scroll_amount >= m_scroll_amount_limit)
    {
        show_right = false;
        m_scroll_amount = m_scroll_amount_limit;
    }
    m_scroll_buttons_visible = show_left || show_right;

    if(show_left)
    {
        wxMemoryDC temp_dc;
        wxSize size;
        long direction;
        if(GetMajorAxis() == wxHORIZONTAL)
        {
              direction = wxRIBBON_SCROLL_BTN_LEFT;
              size = m_art->GetScrollButtonMinimumSize(temp_dc, GetParent(), direction);
              size.SetHeight(GetSize().GetHeight());
        }
        else
        {
              direction = wxRIBBON_SCROLL_BTN_UP;
              size = m_art->GetScrollButtonMinimumSize(temp_dc, GetParent(), direction);
              size.SetWidth(GetSize().GetWidth());
        }
        if (m_scroll_left_btn)
        {
              m_scroll_left_btn->SetSize(size);
        }
        else
        {
              m_scroll_left_btn = new wxRibbonPageScrollButton(this, wxID_ANY, GetPosition(), size, direction);
              reposition = true;
        }
        if(!IsShown())
        {
              m_scroll_left_btn->Hide();
        }
    }
    else
    {
        if(m_scroll_left_btn != nullptr)
        {
            m_scroll_left_btn->Destroy();
            m_scroll_left_btn = nullptr;
            reposition = true;
        }
    }

    if(show_right)
    {
        wxMemoryDC temp_dc;
        wxSize size;
        long direction;
        if(GetMajorAxis() == wxHORIZONTAL)
        {
              direction = wxRIBBON_SCROLL_BTN_RIGHT;
              size = m_art->GetScrollButtonMinimumSize(temp_dc, GetParent(), direction);
              size.SetHeight(GetSize().GetHeight());
        }
        else
        {
              direction = wxRIBBON_SCROLL_BTN_DOWN;
              size = m_art->GetScrollButtonMinimumSize(temp_dc, GetParent(), direction);
              size.SetWidth(GetSize().GetWidth());
        }
        wxPoint initial_pos = GetPosition() + GetSize() - size;
        if (m_scroll_right_btn)
        {
              m_scroll_right_btn->SetSize(size);
        }
        else
        {
              m_scroll_right_btn = new wxRibbonPageScrollButton(this, wxID_ANY, initial_pos, size, direction);
              reposition = true;
        }
        if(!IsShown())
        {
              m_scroll_right_btn->Hide();
        }
    }
    else
    {
        if(m_scroll_right_btn != nullptr)
        {
            m_scroll_right_btn->Destroy();
            m_scroll_right_btn = nullptr;
            reposition = true;
        }
    }

    if(reposition)
    {
        wxASSERT_MSG(wxDynamicCast(GetParent(), wxRibbonBar), "pointer of wrong type?");
        static_cast<wxRibbonBar*>(GetParent())->RepositionPage(this);
    }

    return reposition;
}

static int GetSizeInOrientation(wxSize size, wxOrientation orientation)
{
    switch(orientation)
    {
    case wxHORIZONTAL: return size.GetWidth();
    case wxVERTICAL: return size.GetHeight();
    case wxBOTH: return size.GetWidth() * size.GetHeight();
    default: return 0;
    }
}

bool wxRibbonPage::ExpandPanels(wxOrientation direction, int maximum_amount)
{
    bool expanded_something = false;
    while(maximum_amount > 0)
    {
        int smallest_size = INT_MAX;
        wxRibbonPanel* smallest_panel = nullptr;
        wxSize* smallest_panel_size = nullptr;
        wxSize* panel_size = m_size_calc_array;
        for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
                  node;
                  node = node->GetNext(), ++panel_size )
        {
            wxRibbonPanel* panel = wxDynamicCast(node->GetData(), wxRibbonPanel);
            if(panel == nullptr || !panel->IsShown())
            {
                continue;
            }
            if (panel->GetFlags() & wxRIBBON_PANEL_FLEXIBLE)
            {
                // Don't change if it's flexible since we already calculated the
                // correct size for the panel.
            }
            else if(panel->IsSizingContinuous())
            {
                int size = GetSizeInOrientation(*panel_size, direction);
                if(size < smallest_size)
                {
                    smallest_size = size;
                    smallest_panel = panel;
                    smallest_panel_size = panel_size;
                }
            }
            else
            {
                int size = GetSizeInOrientation(*panel_size, direction);
                if(size < smallest_size)
                {
                    wxSize larger = panel->GetNextLargerSize(direction, *panel_size);
                    if(larger != (*panel_size) && GetSizeInOrientation(larger, direction) > size)
                    {
                        smallest_size = size;
                        smallest_panel = panel;
                        smallest_panel_size = panel_size;
                    }
                }
            }
        }
        if(smallest_panel != nullptr)
        {
            if(smallest_panel->IsSizingContinuous())
            {
                int amount = maximum_amount;
                if(amount > 32)
                {
                    // For "large" growth, grow this panel a bit, and then re-allocate
                    // the remainder (which may come to this panel again anyway)
                    amount = 32;
                }
                if(direction & wxHORIZONTAL)
                {
                    smallest_panel_size->x += amount;
                }
                if(direction & wxVERTICAL)
                {
                    smallest_panel_size->y += amount;
                }
                maximum_amount -= amount;
                m_collapse_stack.Add(smallest_panel);
                expanded_something = true;
            }
            else
            {
                wxSize larger = smallest_panel->GetNextLargerSize(direction, *smallest_panel_size);
                wxSize delta = larger - (*smallest_panel_size);
                if(GetSizeInOrientation(delta, direction) <= maximum_amount)
                {
                    *smallest_panel_size = larger;
                    maximum_amount -= GetSizeInOrientation(delta, direction);
                    m_collapse_stack.Add(smallest_panel);
                    expanded_something = true;
                }
                else
                {
                    break;
                }
            }
        }
        else
        {
            break;
        }
    }
    return expanded_something;
}

bool wxRibbonPage::CollapsePanels(wxOrientation direction, int minimum_amount)
{
    while(minimum_amount > 0)
    {
        wxRibbonPanel* largest_panel = nullptr;
        wxSize* largest_panel_size = nullptr;
        wxSize* panel_size = m_size_calc_array;
        if(!m_collapse_stack.IsEmpty())
        {
            // For a more consistent panel layout, try to collapse panels which
            // were recently expanded.
            largest_panel = wxDynamicCast(m_collapse_stack.Last(), wxRibbonPanel);
            m_collapse_stack.RemoveAt(m_collapse_stack.GetCount() - 1);
            for(wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
                      node;
                      node = node->GetNext(), ++panel_size )
            {
                wxRibbonPanel* panel = wxDynamicCast(node->GetData(), wxRibbonPanel);
                if(panel == largest_panel && panel->IsShown())
                {
                    largest_panel_size = panel_size;
                    break;
                }
            }
        }
        else
        {
            int largest_size = 0;
            for(wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
                      node;
                      node = node->GetNext(), ++panel_size )
            {
                wxRibbonPanel* panel = wxDynamicCast(node->GetData(), wxRibbonPanel);
                if(panel == nullptr || !panel->IsShown())
                {
                    continue;
                }
                if(panel->IsSizingContinuous())
                {
                    int size = GetSizeInOrientation(*panel_size, direction);
                    if(size > largest_size)
                    {
                        largest_size = size;
                        largest_panel = panel;
                        largest_panel_size = panel_size;
                    }
                }
                else
                {
                    int size = GetSizeInOrientation(*panel_size, direction);
                    if(size > largest_size)
                    {
                        wxSize smaller = panel->GetNextSmallerSize(direction, *panel_size);
                        if(smaller != (*panel_size) &&
                            GetSizeInOrientation(smaller, direction) < size)
                        {
                            largest_size = size;
                            largest_panel = panel;
                            largest_panel_size = panel_size;
                        }
                    }
                }
            }
        }
        if(largest_panel != nullptr && largest_panel_size != nullptr)
        {
            if(largest_panel->IsSizingContinuous())
            {
                int amount = minimum_amount;
                if(amount > 32)
                {
                    // For "large" contraction, reduce this panel a bit, and
                    // then re-allocate the remainder of the quota (which may
                    // come to this panel again anyway)
                    amount = 32;
                }
                if(direction & wxHORIZONTAL)
                {
                    largest_panel_size->x -= amount;
                }
                if(direction & wxVERTICAL)
                {
                    largest_panel_size->y -= amount;
                }
                minimum_amount -= amount;
            }
            else
            {
                wxSize smaller = largest_panel->GetNextSmallerSize(direction, *largest_panel_size);
                wxSize delta = (*largest_panel_size) - smaller;
                *largest_panel_size = smaller;
                minimum_amount -= GetSizeInOrientation(delta, direction);
            }
        }
        else
        {
            break;
        }
    }
    return minimum_amount <= 0;
}

wxRibbonPanel* wxRibbonPage::GetPanel(int n)
{
    int currentPanelIndex = 0;
    for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
          node;
          node = node->GetNext() )
    {
        wxRibbonPanel* panel = wxDynamicCast(node->GetData(), wxRibbonPanel);
        if ( panel != nullptr )
        {
            if ( n == currentPanelIndex )
            {
                return panel;
            }
            ++currentPanelIndex;
        }
    }
    return nullptr;
}

wxRibbonPanel* wxRibbonPage::GetPanelById(wxWindowID id)
{
    for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
          node;
          node = node->GetNext() )
    {
        wxRibbonPanel* panel = wxDynamicCast(node->GetData(), wxRibbonPanel);
        if ( panel != nullptr && panel->GetId() == id )
        {
            return panel;
        }
    }
    return nullptr;
}

size_t wxRibbonPage::GetPanelCount() const
{
    size_t panelCount = 0;
    for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
          node;
          node = node->GetNext() )
    {
        if ( node->GetData()->IsKindOf(CLASSINFO(wxRibbonPanel)) )
        {
            ++panelCount;
        }
    }
    return panelCount;
}

bool wxRibbonPage::DismissExpandedPanel()
{
    for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
              node;
              node = node->GetNext() )
    {
        wxRibbonPanel* panel = wxDynamicCast(node->GetData(), wxRibbonPanel);
        if(panel == nullptr || !panel->IsShown())
        {
            continue;
        }
        if(panel->GetExpandedPanel() != nullptr)
        {
            return panel->HideExpanded();
        }
    }
    return false;
}

wxSize wxRibbonPage::GetMinSize() const
{
    wxSize min(wxDefaultCoord, wxDefaultCoord);

    for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
          node;
          node = node->GetNext() )
    {
        wxWindow* child = node->GetData();
        wxSize child_min(child->GetMinSize());

        min.x = wxMax(min.x, child_min.x);
        min.y = wxMax(min.y, child_min.y);
    }

    if(GetMajorAxis() == wxHORIZONTAL)
    {
        min.x = wxDefaultCoord;
        if(min.y != wxDefaultCoord)
        {
            min.y += m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_TOP_SIZE) + m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_BOTTOM_SIZE);
        }
    }
    else
    {
        if(min.x != wxDefaultCoord)
        {
            min.x += m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_LEFT_SIZE) + m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_RIGHT_SIZE);
        }
        min.y = wxDefaultCoord;
    }

    return min;
}

wxSize wxRibbonPage::DoGetBestSize() const
{
    wxSize best(0, 0);
    size_t count = 0;

    if(GetMajorAxis() == wxHORIZONTAL)
    {
        best.y = wxDefaultCoord;

        for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
          node;
          node = node->GetNext() )
        {
            wxWindow* child = node->GetData();
            wxSize child_best(child->GetBestSize());

            if(child_best.x != wxDefaultCoord)
            {
                best.IncBy(child_best.x, 0);
            }
            best.y = wxMax(best.y, child_best.y);

            ++count;
        }

        if(count > 1)
        {
            best.IncBy((count - 1) * m_art->GetMetric(wxRIBBON_ART_PANEL_X_SEPARATION_SIZE), 0);
        }
    }
    else
    {
        best.x = wxDefaultCoord;

        for ( wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
          node;
          node = node->GetNext() )
        {
            wxWindow* child = node->GetData();
            wxSize child_best(child->GetBestSize());

            best.x = wxMax(best.x, child_best.x);
            if(child_best.y != wxDefaultCoord)
            {
                best.IncBy(0, child_best.y);
            }

            ++count;
        }

        if(count > 1)
        {
            best.IncBy(0, (count - 1) * m_art->GetMetric(wxRIBBON_ART_PANEL_Y_SEPARATION_SIZE));
        }
    }

    if(best.x != wxDefaultCoord)
    {
        best.x += m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_LEFT_SIZE) + m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_RIGHT_SIZE);
    }
    if(best.y != wxDefaultCoord)
    {
        best.y += m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_TOP_SIZE) + m_art->GetMetric(wxRIBBON_ART_PAGE_BORDER_BOTTOM_SIZE);
    }
    return best;
}

void wxRibbonPage::HideIfExpanded()
{
    if (auto* const bar = wxCheckedStaticCast<wxRibbonBar>(GetParent()))
        bar->HideIfExpanded();
}

#endif // wxUSE_RIBBON
