/////////////////////////////////////////////////////////////////////////////
// Name:        src/generic/mdig.cpp
// Purpose:     Generic MDI (Multiple Document Interface) classes
// Author:      Hans Van Leemputten
// Modified by: 2008-10-31 Vadim Zeitlin: derive from the base classes
// Created:     29/07/2002
// Copyright:   (c) 2002 Hans Van Leemputten
//              (c) 2008 Vadim Zeitlin
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

// ===========================================================================
// declarations
// ===========================================================================

// ---------------------------------------------------------------------------
// headers
// ---------------------------------------------------------------------------

// For compilers that support precompilation, includes "wx.h".
#include "wx/wxprec.h"


#if wxUSE_MDI

#ifndef WX_PRECOMP
    #include "wx/menu.h"
    #include "wx/intl.h"
    #include "wx/log.h"
#endif //WX_PRECOMP

#include "wx/mdi.h"
#include "wx/generic/mdig.h"
#include "wx/notebook.h"
#include "wx/scopeguard.h"

#include "wx/stockitem.h"

enum MDI_MENU_ID
{
    wxWINDOWCLOSE = 4001,
    wxWINDOWCLOSEALL,
    wxWINDOWNEXT,
    wxWINDOWPREV
};

//-----------------------------------------------------------------------------
// wxGenericMDIParentFrame
//-----------------------------------------------------------------------------

wxIMPLEMENT_DYNAMIC_CLASS(wxGenericMDIParentFrame, wxFrame);

wxBEGIN_EVENT_TABLE(wxGenericMDIParentFrame, wxFrame)
    EVT_CLOSE(wxGenericMDIParentFrame::OnClose)
#if wxUSE_MENUS
    EVT_MENU(wxID_ANY, wxGenericMDIParentFrame::OnWindowMenu)
#endif
wxEND_EVENT_TABLE()

void wxGenericMDIParentFrame::Init()
{
#if wxUSE_MENUS
    m_pMyMenuBar = nullptr;
#endif // wxUSE_MENUS
}

wxGenericMDIParentFrame::~wxGenericMDIParentFrame()
{
    // Make sure the client window is destructed before the menu bars are!
    wxDELETE(m_clientWindow);

#if wxUSE_MENUS
    wxDELETE(m_pMyMenuBar);

    RemoveWindowMenu(GetMenuBar());
#endif // wxUSE_MENUS
}

bool wxGenericMDIParentFrame::Create(wxWindow *parent,
                              wxWindowID id,
                              const wxString& title,
                              const wxPoint& pos,
                              const wxSize& size,
                              long style,
                              const wxString& name)
{
    // this style can be used to prevent a window from having the standard MDI
    // "Window" menu
    if ( !(style & wxFRAME_NO_WINDOW_MENU) )
    {
#if wxUSE_MENUS
        m_windowMenu = new wxMenu;

        m_windowMenu->Append(wxWINDOWCLOSE,    _("Cl&ose"));
        m_windowMenu->Append(wxWINDOWCLOSEALL, _("Close All"));
        m_windowMenu->AppendSeparator();
        m_windowMenu->Append(wxWINDOWNEXT,     _("&Next"));
        m_windowMenu->Append(wxWINDOWPREV,     _("&Previous"));
#endif // wxUSE_MENUS
    }

    // the scrolling styles don't make sense either for us nor for our client
    // window (to which they're supposed to apply)
    style &= ~(wxHSCROLL | wxVSCROLL);

    if ( !wxFrame::Create( parent, id, title, pos, size, style, name ) )
        return false;

    wxGenericMDIClientWindow * const client = OnCreateGenericClient();
    if ( !client->CreateGenericClient(this) )
        return false;

    m_clientWindow = client;

    return true;
}

wxGenericMDIClientWindow *wxGenericMDIParentFrame::OnCreateGenericClient()
{
    return new wxGenericMDIClientWindow;
}

bool wxGenericMDIParentFrame::CloseAll()
{
    wxGenericMDIClientWindow * const client = GetGenericClientWindow();
    if ( !client )
        return true; // none of the windows left

    wxBookCtrlBase * const book = client->GetBookCtrl();
    while ( book->GetPageCount() )
    {
        wxGenericMDIChildFrame * const child = client->GetChild(0);
        if ( !child->Close() )
        {
            // it refused to close, don't close the remaining ones either
            return false;
        }
    }

    return true;
}

#if wxUSE_MENUS
void wxGenericMDIParentFrame::SetWindowMenu(wxMenu* pMenu)
{
    // Replace the window menu from the currently loaded menu bar.
    wxMenuBar *pMenuBar = GetMenuBar();

    if (m_windowMenu)
    {
        RemoveWindowMenu(pMenuBar);

        wxDELETE(m_windowMenu);
    }

    if (pMenu)
    {
        m_windowMenu = pMenu;

        AddWindowMenu(pMenuBar);
    }
}

void wxGenericMDIParentFrame::SetMenuBar(wxMenuBar *pMenuBar)
{
    // Remove the Window menu from the old menu bar
    RemoveWindowMenu(GetMenuBar());
    // Add the Window menu to the new menu bar.
    AddWindowMenu(pMenuBar);

    wxFrame::SetMenuBar(pMenuBar);
}
#endif // wxUSE_MENUS

void wxGenericMDIParentFrame::WXSetChildMenuBar(wxGenericMDIChildFrame *pChild)
{
#if wxUSE_MENUS
    if (pChild  == nullptr)
    {
        // No Child, set Our menu bar back.
        SetMenuBar(m_pMyMenuBar);

        // Make sure we know our menu bar is in use
        m_pMyMenuBar = nullptr;
    }
    else
    {
        if (pChild->GetMenuBar() == nullptr)
            return;

        // Do we need to save the current bar?
        if (m_pMyMenuBar == nullptr)
            m_pMyMenuBar = GetMenuBar();

        SetMenuBar(pChild->GetMenuBar());
    }
#endif // wxUSE_MENUS
}

wxGenericMDIClientWindow *
wxGenericMDIParentFrame::GetGenericClientWindow() const
{
    return static_cast<wxGenericMDIClientWindow *>(m_clientWindow);
}

wxBookCtrlBase *wxGenericMDIParentFrame::GetBookCtrl() const
{
    wxGenericMDIClientWindow * const client = GetGenericClientWindow();
    return client ? client->GetBookCtrl() : nullptr;
}

void wxGenericMDIParentFrame::AdvanceActive(bool forward)
{
    wxBookCtrlBase * const book = GetBookCtrl();
    if ( book )
        book->AdvanceSelection(forward);
}

void wxGenericMDIParentFrame::WXUpdateChildTitle(wxGenericMDIChildFrame *child)
{
    wxGenericMDIClientWindow * const client = GetGenericClientWindow();

    const int pos = client->FindChild(child);
    if ( pos == wxNOT_FOUND )
        return;

    client->GetBookCtrl()->SetPageText(pos, child->GetTitle());
}

void wxGenericMDIParentFrame::WXActivateChild(wxGenericMDIChildFrame *child)
{
    wxGenericMDIClientWindow * const client = GetGenericClientWindow();

    const int pos = client->FindChild(child);
    if ( pos == wxNOT_FOUND )
        return;

    client->GetBookCtrl()->SetSelection(pos);
}

void wxGenericMDIParentFrame::WXRemoveChild(wxGenericMDIChildFrame *child)
{
    const bool removingActive = WXIsActiveChild(child);
    if ( removingActive )
    {
        SetActiveChild(nullptr);
        WXSetChildMenuBar(nullptr);
    }

    wxGenericMDIClientWindow * const client = GetGenericClientWindow();
    wxCHECK_RET( client, "should have client window" );

    wxBookCtrlBase * const book = client->GetBookCtrl();

    // Remove page if still there
    int pos = client->FindChild(child);
    if ( pos != wxNOT_FOUND )
    {
        if ( book->RemovePage(pos) )
            book->Refresh();
    }

    if ( removingActive )
    {
        // Set the new selection to a remaining page
        const size_t count = book->GetPageCount();
        if ( count > (size_t)pos )
        {
            book->SetSelection(pos);
        }
        else
        {
            if ( count > 0 )
                book->SetSelection(count - 1);
        }
    }
}

bool
wxGenericMDIParentFrame::WXIsActiveChild(wxGenericMDIChildFrame *child) const
{
    return static_cast<wxMDIChildFrameBase *>(GetActiveChild()) == child;
}

#if wxUSE_MENUS
void wxGenericMDIParentFrame::RemoveWindowMenu(wxMenuBar *pMenuBar)
{
    if (pMenuBar && m_windowMenu)
    {
        // Remove old window menu
        int pos = pMenuBar->FindMenu(_("&Window"));
        if (pos != wxNOT_FOUND)
        {
            wxASSERT(m_windowMenu == pMenuBar->GetMenu(pos)); // DBG:: We're going to delete the wrong menu!!!
            pMenuBar->Remove(pos);
        }
    }
}

void wxGenericMDIParentFrame::AddWindowMenu(wxMenuBar *pMenuBar)
{
    if (pMenuBar && m_windowMenu)
    {
        int pos = pMenuBar->FindMenu(wxGetStockLabel(wxID_HELP,false));
        if (pos == wxNOT_FOUND)
        {
            pMenuBar->Append(m_windowMenu, _("&Window"));
        }
        else
        {
            pMenuBar->Insert(pos, m_windowMenu, _("&Window"));
        }
    }
}

void wxGenericMDIParentFrame::OnWindowMenu(wxCommandEvent &event)
{
    switch ( event.GetId() )
    {
        case wxWINDOWCLOSE:
            if ( m_currentChild )
                m_currentChild->Close();
            break;

        case wxWINDOWCLOSEALL:
            CloseAll();
            break;

        case wxWINDOWNEXT:
            ActivateNext();
            break;

        case wxWINDOWPREV:
            ActivatePrevious();
            break;

        default:
            event.Skip();
    }
}
#endif // wxUSE_MENUS

void wxGenericMDIParentFrame::OnClose(wxCloseEvent& event)
{
    if ( !CloseAll() )
        event.Veto();
    else
        event.Skip();
}

bool wxGenericMDIParentFrame::ProcessEvent(wxEvent& event)
{
    if ( m_currentChild )
    {
        // the menu events should be given to the child as we show its menu bar
        // as our own
        const wxEventType eventType = event.GetEventType();
        if ( eventType == wxEVT_MENU ||
             eventType == wxEVT_UPDATE_UI )
        {
            // set the flag indicating that this event was forwarded to the
            // child from the parent and so shouldn't be propagated upwards if
            // not processed to avoid infinite loop
            m_childHandler = m_currentChild;
            wxON_BLOCK_EXIT_NULL(m_childHandler);

            if ( m_currentChild->ProcessWindowEvent(event) )
                return true;
        }
    }

    return wxMDIParentFrameBase::ProcessEvent(event);
}

// ----------------------------------------------------------------------------
// wxGenericMDIChildFrame
// ----------------------------------------------------------------------------

wxIMPLEMENT_DYNAMIC_CLASS(wxGenericMDIChildFrame, wxFrame);

wxBEGIN_EVENT_TABLE(wxGenericMDIChildFrame, wxFrame)
#if wxUSE_MENUS
    EVT_MENU_HIGHLIGHT_ALL(wxGenericMDIChildFrame::OnMenuHighlight)
#endif // wxUSE_MENUS

    EVT_CLOSE(wxGenericMDIChildFrame::OnClose)
wxEND_EVENT_TABLE()

void wxGenericMDIChildFrame::Init()
{
#if wxUSE_MENUS
    m_pMenuBar = nullptr;
#endif // wxUSE_MENUS

#if !wxUSE_GENERIC_MDI_AS_NATIVE
    m_mdiParentGeneric = nullptr;
#endif
}

wxGenericMDIChildFrame::~wxGenericMDIChildFrame()
{
    wxGenericMDIParentFrame * const parent = GetGenericMDIParent();

    // it could happen that we don't have a valid parent if we hadn't been ever
    // really created -- but in this case there is nothing else to do either
    if ( parent )
        parent->WXRemoveChild(this);

#if wxUSE_MENUS
    if ( m_pMenuBar )
    {
        // calling WXRemoveChild() above broke the link between the menu bar
        // and the parent, so we need to also remove it explicitly
        if ( parent )
            parent->RemoveChild(m_pMenuBar);

        delete m_pMenuBar;
    }
#endif // wxUSE_MENUS
}

bool wxGenericMDIChildFrame::Create(wxGenericMDIParentFrame *parent,
                                    wxWindowID id,
                                    const wxString& title,
                                    const wxPoint& WXUNUSED(pos),
                                    const wxSize& size,
                                    long WXUNUSED(style),
                                    const wxString& name)
{
    // unfortunately we can't use the base class m_mdiParent field unless
    // wxGenericMDIParentFrame is wxMDIParentFrame
#if wxUSE_GENERIC_MDI_AS_NATIVE
    m_mdiParent = parent;
#else // generic != native
    // leave m_mdiParent null, we don't have it
    m_mdiParentGeneric = parent;
#endif

    wxBookCtrlBase * const book = parent->GetBookCtrl();

    wxASSERT_MSG( book, "Missing MDI client window." );

    // note that we ignore the styles, none of the usual TLW styles apply to
    // this (child) window
    if ( !wxWindow::Create(book, id, wxDefaultPosition, size, 0, name) )
        return false;

    m_title = title;
    book->AddPage(this, title, true);

    return true;
}

#if wxUSE_MENUS
void wxGenericMDIChildFrame::SetMenuBar( wxMenuBar *menu_bar )
{
    wxMenuBar *pOldMenuBar = m_pMenuBar;
    m_pMenuBar = menu_bar;

    if (m_pMenuBar)
    {
        wxGenericMDIParentFrame *parent = GetGenericMDIParent();

        if ( parent )
        {
            m_pMenuBar->SetParent(parent);

            if ( parent->WXIsActiveChild(this) )
            {
                // Replace current menu bars
                if (pOldMenuBar)
                    parent->WXSetChildMenuBar(nullptr);
                parent->WXSetChildMenuBar(this);
            }
        }
    }
}

wxMenuBar *wxGenericMDIChildFrame::GetMenuBar() const
{
    return m_pMenuBar;
}
#endif // wxUSE_MENUS

void wxGenericMDIChildFrame::SetTitle(const wxString& title)
{
    m_title = title;

    wxGenericMDIParentFrame * const parent = GetGenericMDIParent();
    if ( parent )
        parent->WXUpdateChildTitle(this);
    //else: it's ok, we might be not created yet
}

void wxGenericMDIChildFrame::Activate()
{
    wxGenericMDIParentFrame * const parent = GetGenericMDIParent();

    wxCHECK_RET( parent, "can't activate MDI child without parent" );
    parent->WXActivateChild(this);
}

#if wxUSE_MENUS
void wxGenericMDIChildFrame::OnMenuHighlight(wxMenuEvent& event)
{
    wxGenericMDIParentFrame * const parent = GetGenericMDIParent();
    if ( parent)
    {
        // we don't have any help text for this item,
        // but may be the MDI frame does?
        parent->OnMenuHighlight(event);
    }
}
#endif // wxUSE_MENUS

void wxGenericMDIChildFrame::OnClose(wxCloseEvent& WXUNUSED(event))
{
    // we're not a TLW so don't delay the destruction of this window
    delete this;
}

bool wxGenericMDIChildFrame::TryAfter(wxEvent& event)
{
    // we shouldn't propagate the event to the parent if we received it from it
    // in the first place
    wxGenericMDIParentFrame * const parent = GetGenericMDIParent();
    if ( parent && parent->WXIsInsideChildHandler(this) )
        return false;

    return wxTDIChildFrame::TryAfter(event);
}

// ----------------------------------------------------------------------------
// wxGenericMDIClientWindow
// ----------------------------------------------------------------------------

wxIMPLEMENT_DYNAMIC_CLASS(wxGenericMDIClientWindow, wxWindow);

bool
wxGenericMDIClientWindow::CreateGenericClient(wxWindow *parent)
{
    if ( !wxWindow::Create(parent, wxID_ANY) )
        return false;

    m_notebook = new wxNotebook(this, wxID_ANY);
    m_notebook->Bind
                (
                    wxEVT_NOTEBOOK_PAGE_CHANGED,
                    &wxGenericMDIClientWindow::OnPageChanged,
                    this
                );

    // now that we have a notebook to resize, hook up OnSize() too
    Bind(wxEVT_SIZE, &wxGenericMDIClientWindow::OnSize, this);

    return true;
}

wxBookCtrlBase *wxGenericMDIClientWindow::GetBookCtrl() const
{
    return m_notebook;
}

wxGenericMDIChildFrame *wxGenericMDIClientWindow::GetChild(size_t pos) const
{
    return static_cast<wxGenericMDIChildFrame *>(GetBookCtrl()->GetPage(pos));
}

int wxGenericMDIClientWindow::FindChild(wxGenericMDIChildFrame *child) const
{
    wxBookCtrlBase * const book = GetBookCtrl();
    const size_t count = book->GetPageCount();
    for ( size_t pos = 0; pos < count; pos++ )
    {
        if ( book->GetPage(pos) == child )
            return pos;
    }

    return wxNOT_FOUND;
}

void wxGenericMDIClientWindow::PageChanged(int oldSelection, int newSelection)
{
    // Don't do anything if nothing changed
    if (oldSelection == newSelection)
        return;

    // Again check if we really need to do this...
    if (newSelection != -1)
    {
        wxGenericMDIChildFrame * const child = GetChild(newSelection);

        if ( child->GetGenericMDIParent()->WXIsActiveChild(child) )
            return;
    }

    // Notify old active child that it has been deactivated
    if (oldSelection != -1)
    {
        wxGenericMDIChildFrame * const oldChild = GetChild(oldSelection);
        if (oldChild)
        {
            wxActivateEvent event(wxEVT_ACTIVATE, false, oldChild->GetId());
            event.SetEventObject( oldChild );
            oldChild->GetEventHandler()->ProcessEvent(event);
        }
    }

    // Notify new active child that it has been activated
    if (newSelection != -1)
    {
        wxGenericMDIChildFrame * const activeChild = GetChild(newSelection);
        if ( activeChild )
        {
            wxActivateEvent event(wxEVT_ACTIVATE, true, activeChild->GetId());
            event.SetEventObject( activeChild );
            activeChild->GetEventHandler()->ProcessEvent(event);

            wxGenericMDIParentFrame * const
                parent = activeChild->GetGenericMDIParent();

            if ( parent )
            {
                // this is a dirty hack as activeChild is not really a
                // wxMDIChildFrame at all but we still want to store it in the
                // base class m_currentChild field and this will work as long
                // as we only use as wxMDIChildFrameBase pointer (which it is)
                parent->SetActiveChild(
                    reinterpret_cast<wxMDIChildFrame *>(activeChild));
                parent->WXSetChildMenuBar(activeChild);
            }
        }
    }
}

void wxGenericMDIClientWindow::OnPageChanged(wxBookCtrlEvent& event)
{
    PageChanged(event.GetOldSelection(), event.GetSelection());

    event.Skip();
}

void wxGenericMDIClientWindow::OnSize(wxSizeEvent& WXUNUSED(event))
{
    m_notebook->SetSize(GetClientSize());
}

#endif // wxUSE_MDI

