///////////////////////////////////////////////////////////////////////////////
// Name:        src/ribbon/panel.cpp
// Purpose:     Ribbon-style container for a group of related tools / controls
// Author:      Peter Cawley
// Created:     2009-05-25
// Copyright:   (C) Peter Cawley
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

#include "wx/wxprec.h"


#if wxUSE_RIBBON

#include "wx/ribbon/panel.h"
#include "wx/ribbon/art.h"
#include "wx/ribbon/bar.h"
#include "wx/dcbuffer.h"
#include "wx/display.h"
#include "wx/sizer.h"

#ifndef WX_PRECOMP
#include "wx/frame.h"
#endif

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

wxDEFINE_EVENT(wxEVT_RIBBONPANEL_EXTBUTTON_ACTIVATED, wxRibbonPanelEvent);

wxIMPLEMENT_DYNAMIC_CLASS(wxRibbonPanelEvent, wxCommandEvent);

wxIMPLEMENT_CLASS(wxRibbonPanel, wxRibbonControl);

wxBEGIN_EVENT_TABLE(wxRibbonPanel, wxRibbonControl)
    EVT_ENTER_WINDOW(wxRibbonPanel::OnMouseEnter)
    EVT_ERASE_BACKGROUND(wxRibbonPanel::OnEraseBackground)
    EVT_KILL_FOCUS(wxRibbonPanel::OnKillFocus)
    EVT_LEAVE_WINDOW(wxRibbonPanel::OnMouseLeave)
    EVT_MOTION(wxRibbonPanel::OnMotion)
    EVT_LEFT_DOWN(wxRibbonPanel::OnMouseClick)
    EVT_PAINT(wxRibbonPanel::OnPaint)
    EVT_SIZE(wxRibbonPanel::OnSize)
wxEND_EVENT_TABLE()

wxRibbonPanel::wxRibbonPanel() : m_expanded_dummy(nullptr), m_expanded_panel(nullptr)
{
}

wxRibbonPanel::wxRibbonPanel(wxWindow* parent,
                  wxWindowID id,
                  const wxString& label,
                  const wxBitmap& minimised_icon,
                  const wxPoint& pos,
                  const wxSize& size,
                  long style)
    : wxRibbonControl(parent, id, pos, size, wxBORDER_NONE)
{
    CommonInit(label, minimised_icon, style);
}

wxRibbonPanel::~wxRibbonPanel()
{
    if(m_expanded_panel)
    {
        m_expanded_panel->m_expanded_dummy = nullptr;
        m_expanded_panel->GetParent()->Destroy();
    }
}

bool wxRibbonPanel::Create(wxWindow* parent,
                wxWindowID id,
                const wxString& label,
                const wxBitmap& icon,
                const wxPoint& pos,
                const wxSize& size,
                long style)
{
    if(!wxRibbonControl::Create(parent, id, pos, size, wxBORDER_NONE))
    {
        return false;
    }

    CommonInit(label, icon, style);

    return true;
}

void wxRibbonPanel::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);
        }
    }
    if(m_expanded_panel)
        m_expanded_panel->SetArtProvider(art);
}

void wxRibbonPanel::CommonInit(const wxString& label, const wxBitmap& icon, long style)
{
    SetName(label);
    SetLabel(label);

    m_minimised_size = wxDefaultSize; // Unknown / none
    m_smallest_unminimised_size = wxDefaultSize;// Unknown / none for IsFullySpecified()
    m_preferred_expand_direction = wxSOUTH;
    m_expanded_dummy = nullptr;
    m_expanded_panel = nullptr;
    m_flags = style;
    m_minimised_icon = icon;
    m_minimised = false;
    m_hovered = false;
    m_ext_button_hovered = false;

    if(m_art == nullptr)
    {
        wxRibbonControl* parent = wxDynamicCast(GetParent(), wxRibbonControl);
        if(parent != nullptr)
        {
            m_art = parent->GetArtProvider();
        }
    }

    SetAutoLayout(true);
    SetBackgroundStyle(wxBG_STYLE_PAINT);
    SetMinSize(wxSize(20, 20));
}

bool wxRibbonPanel::IsMinimised() const
{
    return m_minimised;
}

bool wxRibbonPanel::IsHovered() const
{
    return m_hovered;
}

bool wxRibbonPanel::IsExtButtonHovered() const
{
    return m_ext_button_hovered;
}

void wxRibbonPanel::OnMouseEnter(wxMouseEvent& evt)
{
    TestPositionForHover(evt.GetPosition());
}

void wxRibbonPanel::OnMouseEnterChild(wxMouseEvent& evt)
{
    wxPoint pos = evt.GetPosition();
    wxWindow *child = wxDynamicCast(evt.GetEventObject(), wxWindow);
    if(child)
    {
        pos += child->GetPosition();
        TestPositionForHover(pos);
    }
    evt.Skip();
}

void wxRibbonPanel::OnMouseLeave(wxMouseEvent& evt)
{
    TestPositionForHover(evt.GetPosition());
}

void wxRibbonPanel::OnMouseLeaveChild(wxMouseEvent& evt)
{
    wxPoint pos = evt.GetPosition();
    wxWindow *child = wxDynamicCast(evt.GetEventObject(), wxWindow);
    if(child)
    {
        pos += child->GetPosition();
        TestPositionForHover(pos);
    }
    evt.Skip();
}

void wxRibbonPanel::OnMotion(wxMouseEvent& evt)
{
    TestPositionForHover(evt.GetPosition());
}

void wxRibbonPanel::TestPositionForHover(const wxPoint& pos)
{
    bool hovered = false, ext_button_hovered = false;
    if(pos.x >= 0 && pos.y >= 0)
    {
        wxSize size = GetSize();
        if(pos.x < size.GetWidth() && pos.y < size.GetHeight())
        {
            hovered = true;
        }
    }
    if(hovered)
    {
        if(HasExtButton())
            ext_button_hovered = m_ext_button_rect.Contains(pos);
        else
            ext_button_hovered = false;
    }
    if(hovered != m_hovered || ext_button_hovered != m_ext_button_hovered)
    {
        m_hovered = hovered;
        m_ext_button_hovered = ext_button_hovered;
        Refresh(false);
    }
}

void wxRibbonPanel::AddChild(wxWindowBase *child)
{
    wxRibbonControl::AddChild(child);

    // Window enter / leave events count for only the window in question, not
    // for children of the window. The panel wants to be in the hovered state
    // whenever the mouse cursor is within its boundary, so the events need to
    // be attached to children too.
    child->Bind(wxEVT_ENTER_WINDOW, &wxRibbonPanel::OnMouseEnterChild, this);
    child->Bind(wxEVT_LEAVE_WINDOW, &wxRibbonPanel::OnMouseLeaveChild, this);
}

void wxRibbonPanel::RemoveChild(wxWindowBase *child)
{
    child->Unbind(wxEVT_ENTER_WINDOW, &wxRibbonPanel::OnMouseEnterChild, this);
    child->Unbind(wxEVT_LEAVE_WINDOW, &wxRibbonPanel::OnMouseLeaveChild, this);

    wxRibbonControl::RemoveChild(child);
}

bool wxRibbonPanel::HasExtButton()const
{
    wxRibbonBar* bar = GetAncestorRibbonBar();
    if(bar==nullptr)
        return false;
    return (m_flags & wxRIBBON_PANEL_EXT_BUTTON) &&
        (bar->GetWindowStyleFlag() & wxRIBBON_BAR_SHOW_PANEL_EXT_BUTTONS);
}

void wxRibbonPanel::OnSize(wxSizeEvent& evt)
{
    if(GetAutoLayout())
        Layout();

    evt.Skip();
}

void wxRibbonPanel::DoSetSize(int x, int y, int width, int height, int sizeFlags)
{
    // At least on MSW, changing the size of a window will cause GetSize() to
    // report the new size, but a size event may not be handled immediately.
    // If this minimised check was performed in the OnSize handler, then
    // GetSize() could return a size much larger than the minimised size while
    // IsMinimised() returns true. This would then affect layout, as the panel
    // will refuse to grow any larger while in limbo between minimised and non.

    bool minimised = (m_flags & wxRIBBON_PANEL_NO_AUTO_MINIMISE) == 0 &&
        IsMinimised(wxSize(width, height)); // check if would be at this size
    if(minimised != m_minimised)
    {
        m_minimised = minimised;
        // Note that for sizers, this routine disallows the use of mixed shown
        // and hidden controls
        // TODO ? use some list of user set invisible children to restore status.
        for (wxWindowList::compatibility_iterator node = GetChildren().GetFirst();
                  node;
                  node = node->GetNext())
        {
            node->GetData()->Show(!minimised);
        }

        Refresh();
    }

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

// Checks if panel would be minimised at (client size) at_size
bool wxRibbonPanel::IsMinimised(wxSize at_size) const
{
    if(GetSizer())
    {
        // we have no information on size change direction
        // so check both
        wxSize size = GetMinNotMinimisedSize();
        if(size.x > at_size.x || size.y > at_size.y)
            return true;

        return false;
    }

    if(!m_minimised_size.IsFullySpecified())
        return false;

    return (at_size.GetX() < m_minimised_size.GetX() &&
        at_size.GetY() < m_minimised_size.GetY()) ||
        at_size.GetX() < m_smallest_unminimised_size.GetX() ||
        at_size.GetY() < m_smallest_unminimised_size.GetY();
}

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

void wxRibbonPanel::OnPaint(wxPaintEvent& WXUNUSED(evt))
{
    wxAutoBufferedPaintDC dc(this);

    if(m_art != nullptr)
    {
        if(IsMinimised())
        {
            m_art->DrawMinimisedPanel(dc, this, GetSize(), m_minimised_icon_resized);
        }
        else
        {
            m_art->DrawPanelBackground(dc, this, GetSize());
        }
    }
}

bool wxRibbonPanel::IsSizingContinuous() const
{
    // A panel never sizes continuously, even if all of its children can,
    // as it would appear out of place along side non-continuous panels.

    // JS 2012-03-09: introducing wxRIBBON_PANEL_STRETCH to allow
    // the panel to fill its parent page. For example we might have
    // a list of styles in one of the pages, which should stretch to
    // fill available space.
    return (m_flags & wxRIBBON_PANEL_STRETCH) != 0;
}

// Finds the best width and height given the parent's width and height
wxSize wxRibbonPanel::GetBestSizeForParentSize(const wxSize& parentSize) const
{
    if (!IsShown())
    {
        return wxSize(0, 0);
    }
    if (GetChildren().GetCount() == 1)
    {
        wxWindow* win = GetChildren().GetFirst()->GetData();
        wxRibbonControl* control = wxDynamicCast(win, wxRibbonControl);
        if (control)
        {
            wxInfoDC temp_dc(const_cast<wxRibbonPanel*>(this));
            wxSize clientParentSize = m_art->GetPanelClientSize(temp_dc, this, parentSize, nullptr);
            wxSize childSize = control->GetBestSizeForParentSize(clientParentSize);
            wxSize overallSize = m_art->GetPanelSize(temp_dc, this, childSize, nullptr);
            return overallSize;
        }
    }
    return GetSize();
}

wxSize wxRibbonPanel::DoGetNextSmallerSize(wxOrientation direction,
                                         wxSize relative_to) const
{
    if(m_expanded_panel != nullptr)
    {
        // Next size depends upon children, who are currently in the
        // expanded panel
        return m_expanded_panel->DoGetNextSmallerSize(direction, relative_to);
    }

    if(m_art != nullptr)
    {
        wxInfoDC dc(const_cast<wxRibbonPanel*>(this));
        wxSize child_relative = m_art->GetPanelClientSize(dc, this, relative_to, nullptr);
        wxSize smaller(-1, -1);
        bool minimise = false;

        if(GetSizer())
        {
            // Get smallest non minimised size
            smaller = GetMinSize();
            // and adjust to child_relative for parent page
            if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
            {
                 minimise = (child_relative.y <= smaller.y);
                 if(smaller.x < child_relative.x)
                    smaller.x = child_relative.x;
            }
            else
            {
                minimise = (child_relative.x <= smaller.x);
                if(smaller.y < child_relative.y)
                    smaller.y = child_relative.y;
            }
        }
        else if(GetChildren().GetCount() == 1)
        {
            // Simple (and common) case of single ribbon child or Sizer
            wxWindow* child = GetChildren().Item(0)->GetData();
            wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
            if(ribbon_child != nullptr)
            {
                smaller = ribbon_child->GetNextSmallerSize(direction, child_relative);
                minimise = (smaller == child_relative);
            }
        }

        if(minimise)
        {
            if(CanAutoMinimise())
            {
                wxSize minimised = m_minimised_size;
                switch(direction)
                {
                case wxHORIZONTAL:
                    minimised.SetHeight(relative_to.GetHeight());
                    break;
                case wxVERTICAL:
                    minimised.SetWidth(relative_to.GetWidth());
                    break;
                default:
                    break;
                }
                return minimised;
            }
            else
            {
                return relative_to;
            }
        }
        else if(smaller.IsFullySpecified()) // Use fallback if !(sizer/child = 1)
        {
            return m_art->GetPanelSize(dc, this, smaller, nullptr);
        }
    }

    // Fallback: Decrease by 20% (or minimum size, whichever larger)
    wxSize current(relative_to);
    wxSize minimum(GetMinSize());
    if(direction & wxHORIZONTAL)
    {
        current.x = (current.x * 4) / 5;
        if(current.x < minimum.x)
        {
            current.x = minimum.x;
        }
    }
    if(direction & wxVERTICAL)
    {
        current.y = (current.y * 4) / 5;
        if(current.y < minimum.y)
        {
            current.y = minimum.y;
        }
    }
    return current;
}

wxSize wxRibbonPanel::DoGetNextLargerSize(wxOrientation direction,
                                        wxSize relative_to) const
{
    if(m_expanded_panel != nullptr)
    {
        // Next size depends upon children, who are currently in the
        // expanded panel
        return m_expanded_panel->DoGetNextLargerSize(direction, relative_to);
    }

    if(IsMinimised(relative_to))
    {
        wxSize current = relative_to;
        wxSize min_size = GetMinNotMinimisedSize();
        switch(direction)
        {
        case wxHORIZONTAL:
            if(min_size.x > current.x && min_size.y == current.y)
                return min_size;
            break;
        case wxVERTICAL:
            if(min_size.x == current.x && min_size.y > current.y)
                return min_size;
            break;
        case wxBOTH:
            if(min_size.x > current.x && min_size.y > current.y)
                return min_size;
            break;
        default:
            break;
        }
    }

    if(m_art != nullptr)
    {
        wxInfoDC dc(const_cast<wxRibbonPanel*>(this));
        wxSize child_relative = m_art->GetPanelClientSize(dc, this, relative_to, nullptr);
        wxSize larger(-1, -1);

        if(GetSizer())
        {
            // We could just let the sizer expand in flow direction but see comment
            // in IsSizingContinuous()
            larger = GetPanelSizerBestSize();
            // and adjust for page in non flow direction
            if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
            {
                 if(larger.x != child_relative.x)
                    larger.x = child_relative.x;
            }
            else if(larger.y != child_relative.y)
            {
                larger.y = child_relative.y;
            }
        }
        else if(GetChildren().GetCount() == 1)
        {
            // Simple (and common) case of single ribbon child
            wxWindow* child = GetChildren().Item(0)->GetData();
            wxRibbonControl* ribbon_child = wxDynamicCast(child, wxRibbonControl);
            if(ribbon_child != nullptr)
            {
                larger = ribbon_child->GetNextLargerSize(direction, child_relative);
            }
        }

        if(larger.IsFullySpecified()) // Use fallback if !(sizer/child = 1)
        {
            if(larger == child_relative)
            {
                return relative_to;
            }
            else
            {
                return m_art->GetPanelSize(dc, this, larger, nullptr);
            }
        }
    }

    // Fallback: Increase by 25% (equal to a prior or subsequent 20% decrease)
    // Note that due to rounding errors, this increase may not exactly equal a
    // matching decrease - an ideal solution would not have these errors, but
    // avoiding them is non-trivial unless an increase is by 100% rather than
    // a fractional amount. This would then be non-ideal as the resizes happen
    // at very large intervals.
    wxSize current(relative_to);
    if(direction & wxHORIZONTAL)
    {
        current.x = (current.x * 5 + 3) / 4;
    }
    if(direction & wxVERTICAL)
    {
        current.y = (current.y * 5 + 3) / 4;
    }
    return current;
}

bool wxRibbonPanel::CanAutoMinimise() const
{
    return (m_flags & wxRIBBON_PANEL_NO_AUTO_MINIMISE) == 0
        && m_minimised_size.IsFullySpecified();
}

wxSize wxRibbonPanel::GetMinSize() const
{
    if (!IsShown())
    {
        return wxSize(0, 0);
    }
    if(m_expanded_panel != nullptr)
    {
        // Minimum size depends upon children, who are currently in the
        // expanded panel
        return m_expanded_panel->GetMinSize();
    }

    if(CanAutoMinimise())
    {
        return m_minimised_size;
    }
    else
    {
        return GetMinNotMinimisedSize();
    }
}

wxSize wxRibbonPanel::GetMinNotMinimisedSize() const
{
    if (!IsShown())
    {
        return wxSize(0, 0);
    }
    // Ask sizer if present
    if(GetSizer())
    {
        wxInfoDC dc(const_cast<wxRibbonPanel*>(this));
        return m_art->GetPanelSize(dc, this, GetPanelSizerMinSize(), nullptr);
    }
    else if(GetChildren().GetCount() == 1)
    {
        // Common case of single child taking up the entire panel
        wxWindow* child = GetChildren().Item(0)->GetData();
        wxInfoDC dc(const_cast<wxRibbonPanel*>(this));
        return m_art->GetPanelSize(dc, this, child->GetMinSize(), nullptr);
    }

    return wxRibbonControl::GetMinSize();
}

wxSize wxRibbonPanel::GetPanelSizerMinSize() const
{
    // Called from Realize() to set m_smallest_unminimised_size and from other
    // functions to get the minimum size.
    // The panel will be invisible when minimised and sizer calcs will be 0
    // Uses m_smallest_unminimised_size in preference to GetSizer()->CalcMin()
    // to eliminate flicker.

    // Check if is visible and not previously calculated
    if(IsShown() && !m_smallest_unminimised_size.IsFullySpecified())
    {
         return GetSizer()->CalcMin();
    }
    // else use previously calculated m_smallest_unminimised_size
    wxInfoDC dc(const_cast<wxRibbonPanel*>(this));
    return m_art->GetPanelClientSize(dc,
                                    this,
                                    m_smallest_unminimised_size,
                                    nullptr);
}

wxSize wxRibbonPanel::GetPanelSizerBestSize() const
{
    wxSize size = GetPanelSizerMinSize();
    // TODO allow panel to increase its size beyond minimum size
    // by steps similarly to ribbon control panels (preferred for aesthetics)
    // or continuously.
    return size;
}

wxSize wxRibbonPanel::DoGetBestSize() const
{
    // Ask sizer if present
    if( GetSizer())
    {
        wxInfoDC dc(const_cast<wxRibbonPanel*>(this));
        return m_art->GetPanelSize(dc, this, GetPanelSizerBestSize(), nullptr);
    }
    else if(GetChildren().GetCount() == 1)
    {
        // Common case of no sizer and single child taking up the entire panel
        wxWindow* child = GetChildren().Item(0)->GetData();
        wxInfoDC dc(const_cast<wxRibbonPanel*>(this));
        return m_art->GetPanelSize(dc, this, child->GetBestSize(), nullptr);
    }

    return wxRibbonControl::DoGetBestSize();
}

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

    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;
        }
    }

    wxSize minimum_children_size(0, 0);

    // Reset it before calling GetPanelSizerMinSize() below as it shouldn't use
    // the old value, if we had any.
    m_smallest_unminimised_size = wxDefaultSize;

    // Ask sizer if there is one present
    if(GetSizer())
    {
        minimum_children_size = GetPanelSizerMinSize();
    }
    else if(GetChildren().GetCount() == 1)
    {
        minimum_children_size = GetChildren().GetFirst()->GetData()->GetMinSize();
    }

    if(m_art != nullptr)
    {
        wxInfoDC temp_dc(this);

        m_smallest_unminimised_size =
            m_art->GetPanelSize(temp_dc, this, minimum_children_size, nullptr);

        wxSize bitmap_size;
        wxSize panel_min_size = GetMinNotMinimisedSize();
        m_minimised_size = m_art->GetMinimisedPanelMinimumSize(temp_dc, this,
            &bitmap_size, &m_preferred_expand_direction);
        if(m_minimised_icon.IsOk() && m_minimised_icon.GetLogicalSize() != bitmap_size)
        {
            double scale = m_minimised_icon.GetScaleFactor();
            if (scale > 1.0)
                scale = 2.0;

            wxImage img(m_minimised_icon.ConvertToImage());
            img.Rescale(wxRound(scale * bitmap_size.GetWidth()),
                        wxRound(scale * bitmap_size.GetHeight()),
                        wxIMAGE_QUALITY_HIGH);
            m_minimised_icon_resized = wxBitmap(img, -1, scale);
        }
        else
        {
            m_minimised_icon_resized = m_minimised_icon;
        }
        if(m_minimised_size.x > panel_min_size.x &&
            m_minimised_size.y > panel_min_size.y)
        {
            // No point in having a minimised size which is larger than the
            // minimum size which the children can go to.
            m_minimised_size = wxSize(-1, -1);
        }
        else
        {
            if(m_art->GetFlags() & wxRIBBON_BAR_FLOW_VERTICAL)
            {
                m_minimised_size.x = panel_min_size.x;
            }
            else
            {
                m_minimised_size.y = panel_min_size.y;
            }
        }
    }
    else
    {
        m_minimised_size = wxSize(-1, -1);
    }

    return Layout() && status;
}

bool wxRibbonPanel::Layout()
{
    if(IsMinimised())
    {
        // Children are all invisible when minimised
        return true;
    }

    // Get wxRibbonPanel client size
    wxPoint position;
    wxInfoDC dc(this);
    wxSize size = m_art->GetPanelClientSize(dc, this, GetSize(), &position);

    // If there is a sizer, use it
    if(GetSizer())
    {
        GetSizer()->SetDimension(position, size); // SetSize and Layout()
    }
    else if(GetChildren().GetCount() == 1)
    {
        // Common case of no sizer and single child taking up the entire panel
        wxWindow* child = GetChildren().Item(0)->GetData();
        child->SetSize(position.x, position.y, size.GetWidth(), size.GetHeight());
    }

    if(HasExtButton())
        m_ext_button_rect = m_art->GetPanelExtButtonArea(dc, this, GetSize());

    return true;
}

void wxRibbonPanel::OnMouseClick(wxMouseEvent& WXUNUSED(evt))
{
    if(IsMinimised())
    {
        if(m_expanded_panel != nullptr)
        {
            HideExpanded();
        }
        else
        {
            ShowExpanded();
        }
    }
    else if(IsExtButtonHovered())
    {
        wxRibbonPanelEvent notification(wxEVT_RIBBONPANEL_EXTBUTTON_ACTIVATED, GetId());
        notification.SetEventObject(this);
        notification.SetPanel(this);
        ProcessEvent(notification);
    }
}

wxRibbonPanel* wxRibbonPanel::GetExpandedDummy()
{
    return m_expanded_dummy;
}

wxRibbonPanel* wxRibbonPanel::GetExpandedPanel()
{
    return m_expanded_panel;
}

bool wxRibbonPanel::ShowExpanded()
{
    if(!IsMinimised())
    {
        return false;
    }
    if(m_expanded_dummy != nullptr || m_expanded_panel != nullptr)
    {
        return false;
    }

    wxSize size = GetBestSize();

    // Special case for flexible panel layout, where GetBestSize doesn't work
    if (GetFlags() & wxRIBBON_PANEL_FLEXIBLE)
    {
        size = GetBestSizeForParentSize(wxSize(400, 1000));
    }

    wxPoint pos = GetExpandedPosition(wxRect(GetScreenPosition(), GetSize()),
        size, m_preferred_expand_direction).GetTopLeft();

    // Need a top-level frame to contain the expanded panel
    wxFrame *container = new wxFrame(nullptr, wxID_ANY, GetLabel(),
        pos, size, wxFRAME_NO_TASKBAR | wxBORDER_NONE);

    m_expanded_panel = new wxRibbonPanel(container, wxID_ANY,
        GetLabel(), m_minimised_icon, wxPoint(0, 0), size, (m_flags /* & ~wxRIBBON_PANEL_FLEXIBLE */));

    m_expanded_panel->SetArtProvider(m_art);
    m_expanded_panel->m_expanded_dummy = this;

    // Move all children to the new panel.
    // Conceptually it might be simpler to reparent this entire panel to the
    // container and create a new panel to sit in its place while expanded.
    // This approach has a problem though - when the panel is reinserted into
    // its original parent, it'll be at a different position in the child list
    // and thus assume a new position.
    // NB: Children iterators not used as behaviour is not well defined
    // when iterating over a container which is being emptied
    while(!GetChildren().IsEmpty())
    {
        wxWindow *child = GetChildren().GetFirst()->GetData();
        child->Reparent(m_expanded_panel);
        child->Show();
    }

    // Move sizer to new panel
    if(GetSizer())
    {
        wxSizer* sizer = GetSizer();
        SetSizer(nullptr, false);
        m_expanded_panel->SetSizer(sizer);
    }

    m_expanded_panel->Realize();
    Refresh();
    container->SetMinClientSize(size);
    container->Show();
    m_expanded_panel->SetFocus();

    return true;
}

bool wxRibbonPanel::ShouldSendEventToDummy(wxEvent& evt)
{
    // For an expanded panel, filter events between being sent up to the
    // floating top level window or to the dummy panel sitting in the ribbon
    // bar.

    // Child focus events should not be redirected, as the child would not be a
    // child of the window the event is redirected to. All other command events
    // seem to be suitable for redirecting.
    return evt.IsCommandEvent() && evt.GetEventType() != wxEVT_CHILD_FOCUS;
}

bool wxRibbonPanel::TryAfter(wxEvent& evt)
{
    if(m_expanded_dummy && ShouldSendEventToDummy(evt))
    {
        wxPropagateOnce propagateOnce(evt);
        return m_expanded_dummy->GetEventHandler()->ProcessEvent(evt);
    }
    else
    {
        return wxRibbonControl::TryAfter(evt);
    }
}

static bool IsAncestorOf(wxWindow *ancestor, wxWindow *window)
{
    while(window != nullptr)
    {
        wxWindow *parent = window->GetParent();
        if(parent == ancestor)
            return true;
        else
            window = parent;
    }
    return false;
}

void wxRibbonPanel::OnKillFocus(wxFocusEvent& evt)
{
    if(m_expanded_dummy)
    {
        wxWindow *receiver = evt.GetWindow();
        if(IsAncestorOf(this, receiver))
        {
            m_child_with_focus = receiver;
            receiver->Bind(wxEVT_KILL_FOCUS,
                &wxRibbonPanel::OnChildKillFocus, this);
        }
        else if(receiver == nullptr || receiver != m_expanded_dummy)
        {
            HideExpanded();
        }
    }
}

void wxRibbonPanel::OnChildKillFocus(wxFocusEvent& evt)
{
    if(m_child_with_focus == nullptr)
        return; // Should never happen, but a check can't hurt

    m_child_with_focus->Unbind(wxEVT_KILL_FOCUS,
      &wxRibbonPanel::OnChildKillFocus, this);
    m_child_with_focus = nullptr;

    wxWindow *receiver = evt.GetWindow();
    if(receiver == this || IsAncestorOf(this, receiver))
    {
        m_child_with_focus = receiver;
        receiver->Bind(wxEVT_KILL_FOCUS,
            &wxRibbonPanel::OnChildKillFocus, this);
        evt.Skip();
    }
    else if(receiver == nullptr || receiver != m_expanded_dummy)
    {
        HideExpanded();
        // Do not skip event, as the panel has been de-expanded, causing the
        // child with focus to be reparented (and hidden). If the event
        // continues propagation then bad things happen.
    }
    else
    {
        evt.Skip();
    }
}

bool wxRibbonPanel::HideExpanded()
{
    if(m_expanded_dummy == nullptr)
    {
        if(m_expanded_panel)
        {
            return m_expanded_panel->HideExpanded();
        }
        else
        {
            return false;
        }
    }

    // Move children back to original panel
    // NB: Children iterators not used as behaviour is not well defined
    // when iterating over a container which is being emptied
    while(!GetChildren().IsEmpty())
    {
        wxWindow *child = GetChildren().GetFirst()->GetData();
        child->Reparent(m_expanded_dummy);
        child->Hide();
    }

    // Move sizer back
    if(GetSizer())
    {
        wxSizer* sizer = GetSizer();
        SetSizer(nullptr, false);
        m_expanded_dummy->SetSizer(sizer);
    }

    m_expanded_dummy->m_expanded_panel = nullptr;
    m_expanded_dummy->Realize();
    m_expanded_dummy->Refresh();
    wxWindow *parent = GetParent();
    Destroy();
    parent->Destroy();

    return true;
}

wxRect wxRibbonPanel::GetExpandedPosition(wxRect panel,
                                          wxSize expanded_size,
                                          wxDirection direction)
{
    // Strategy:
    // 1) Determine primary position based on requested direction
    // 2) Move the position so that it sits entirely within a display
    //    (for single monitor systems, this moves it into the display region,
    //     but for multiple monitors, it does so without splitting it over
    //     more than one display)
    // 2.1) Move in the primary axis
    // 2.2) Move in the secondary axis

    wxPoint pos;
    bool primary_x = false;
    int secondary_x = 0;
    int secondary_y = 0;
    switch(direction)
    {
    case wxNORTH:
        pos.x = panel.GetX() + (panel.GetWidth() - expanded_size.GetWidth()) / 2;
        pos.y = panel.GetY() - expanded_size.GetHeight();
        primary_x = true;
        secondary_y = 1;
        break;
    case wxEAST:
        pos.x = panel.GetRight();
        pos.y = panel.GetY() + (panel.GetHeight() - expanded_size.GetHeight()) / 2;
        secondary_x = -1;
        break;
    case wxSOUTH:
        pos.x = panel.GetX() + (panel.GetWidth() - expanded_size.GetWidth()) / 2;
        pos.y = panel.GetBottom();
        primary_x = true;
        secondary_y = -1;
        break;
    case wxWEST:
    default:
        pos.x = panel.GetX() - expanded_size.GetWidth();
        pos.y = panel.GetY() + (panel.GetHeight() - expanded_size.GetHeight()) / 2;
        secondary_x = 1;
        break;
    }
    wxRect expanded(pos, expanded_size);

    wxRect best(expanded);
    int best_distance = INT_MAX;

    const unsigned display_n = wxDisplay::GetCount();
    unsigned display_i;
    for(display_i = 0; display_i < display_n; ++display_i)
    {
        wxRect display = wxDisplay(display_i).GetGeometry();

        if(display.Contains(expanded))
        {
            return expanded;
        }
        else if(display.Intersects(expanded))
        {
            wxRect new_rect(expanded);
            int distance = 0;

            if(primary_x)
            {
                if(expanded.GetRight() > display.GetRight())
                {
                    distance = expanded.GetRight() - display.GetRight();
                    new_rect.x -= distance;
                }
                else if(expanded.GetLeft() < display.GetLeft())
                {
                    distance = display.GetLeft() - expanded.GetLeft();
                    new_rect.x += distance;
                }
            }
            else
            {
                if(expanded.GetBottom() > display.GetBottom())
                {
                    distance = expanded.GetBottom() - display.GetBottom();
                    new_rect.y -= distance;
                }
                else if(expanded.GetTop() < display.GetTop())
                {
                    distance = display.GetTop() - expanded.GetTop();
                    new_rect.y += distance;
                }
            }
            if(!display.Contains(new_rect))
            {
                // Tried moving in primary axis, but failed.
                // Hence try moving in the secondary axis.
                int dx = secondary_x * (panel.GetWidth() + expanded_size.GetWidth());
                int dy = secondary_y * (panel.GetHeight() + expanded_size.GetHeight());
                new_rect.x += dx;
                new_rect.y += dy;

                // Squaring makes secondary moves more expensive (and also
                // prevents a negative cost)
                distance += dx * dx + dy * dy;
            }
            if(display.Contains(new_rect) && distance < best_distance)
            {
                best = new_rect;
                best_distance = distance;
            }
        }
    }

    return best;
}

void wxRibbonPanel::HideIfExpanded()
{
    wxRibbonPage* const containingPage = wxDynamicCast(m_parent, wxRibbonPage);
    if (containingPage)
        containingPage->HideIfExpanded();
}

#endif // wxUSE_RIBBON
