///////////////////////////////////////////////////////////////////////////////
// Name:        src/generic/wizard.cpp
// Purpose:     generic implementation of wxWizard class
// Author:      Vadim Zeitlin
// Modified by: Robert Cavanaugh
//              1) Added capability for wxWizardPage to accept resources
//              2) Added "Help" button handler stub
//              3) Fixed ShowPage() bug on displaying bitmaps
//              Robert Vazan (sizers)
// Created:     15.08.99
// Copyright:   (c) 1999 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

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

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

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


#if wxUSE_WIZARDDLG

#ifndef WX_PRECOMP
    #include "wx/dynarray.h"
    #include "wx/intl.h"
    #include "wx/statbmp.h"
    #include "wx/button.h"
    #include "wx/settings.h"
    #include "wx/sizer.h"
#endif //WX_PRECOMP

#include "wx/statline.h"

#include "wx/scrolwin.h"
#include "wx/wizard.h"
#include "wx/dcmemory.h"

// ----------------------------------------------------------------------------
// wxWizardSizer
// ----------------------------------------------------------------------------

class wxWizardSizer : public wxSizer
{
public:
    wxWizardSizer(wxWizard *owner);

    virtual wxSizerItem *Insert(size_t index, wxSizerItem *item) override;

    virtual void RecalcSizes() override;
    virtual wxSize CalcMin() override;

    // get the max size of all wizard pages
    wxSize GetMaxChildSize();

    // return the border which can be either set using wxWizard::SetBorder() or
    // have default value
    int GetBorder() const;

private:
    wxSize SiblingSize(wxSizerItem *child);

    wxWizard *m_owner;
    wxSize m_childSize;
};

// ----------------------------------------------------------------------------
// event tables and such
// ----------------------------------------------------------------------------

wxDEFINE_EVENT( wxEVT_WIZARD_PAGE_CHANGED, wxWizardEvent );
wxDEFINE_EVENT( wxEVT_WIZARD_PAGE_CHANGING, wxWizardEvent );
wxDEFINE_EVENT( wxEVT_WIZARD_BEFORE_PAGE_CHANGED, wxWizardEvent );
wxDEFINE_EVENT( wxEVT_WIZARD_CANCEL, wxWizardEvent );
wxDEFINE_EVENT( wxEVT_WIZARD_FINISHED, wxWizardEvent );
wxDEFINE_EVENT( wxEVT_WIZARD_HELP, wxWizardEvent );
wxDEFINE_EVENT( wxEVT_WIZARD_PAGE_SHOWN, wxWizardEvent );

wxBEGIN_EVENT_TABLE(wxWizard, wxDialog)
    EVT_BUTTON(wxID_CANCEL, wxWizard::OnCancel)
    EVT_BUTTON(wxID_BACKWARD, wxWizard::OnBackOrNext)
    EVT_BUTTON(wxID_FORWARD, wxWizard::OnBackOrNext)
    EVT_BUTTON(wxID_HELP, wxWizard::OnHelp)

    EVT_WIZARD_PAGE_CHANGED(wxID_ANY, wxWizard::OnWizEvent)
    EVT_WIZARD_PAGE_CHANGING(wxID_ANY, wxWizard::OnWizEvent)
    EVT_WIZARD_CANCEL(wxID_ANY, wxWizard::OnWizEvent)
    EVT_WIZARD_FINISHED(wxID_ANY, wxWizard::OnWizEvent)
    EVT_WIZARD_HELP(wxID_ANY, wxWizard::OnWizEvent)
wxEND_EVENT_TABLE()

wxIMPLEMENT_DYNAMIC_CLASS(wxWizard, wxDialog);

/*
    TODO PROPERTIES :
    wxWizard
        extstyle
        title
*/

wxIMPLEMENT_ABSTRACT_CLASS(wxWizardPage, wxPanel);
wxIMPLEMENT_DYNAMIC_CLASS(wxWizardPageSimple, wxWizardPage);
wxIMPLEMENT_DYNAMIC_CLASS(wxWizardEvent, wxNotifyEvent);

// ============================================================================
// implementation
// ============================================================================

// ----------------------------------------------------------------------------
// wxWizardPage
// ----------------------------------------------------------------------------

void wxWizardPage::Init()
{
    m_bitmap = wxBitmapBundle();
}

wxWizardPage::wxWizardPage(wxWizard *parent,
                           const wxBitmapBundle& bitmap)
{
    Create(parent, bitmap);
}

bool wxWizardPage::Create(wxWizard *parent,
                          const wxBitmapBundle& bitmap)
{
    if ( !wxPanel::Create(parent, wxID_ANY) )
        return false;

    m_bitmap = bitmap;

    // initially the page is hidden, it's shown only when it becomes current
    Hide();

    return true;
}

// ----------------------------------------------------------------------------
// wxWizardPageSimple
// ----------------------------------------------------------------------------

wxWizardPage *wxWizardPageSimple::GetPrev() const
{
    return m_prev;
}

wxWizardPage *wxWizardPageSimple::GetNext() const
{
    return m_next;
}

// ----------------------------------------------------------------------------
// wxWizardSizer
// ----------------------------------------------------------------------------

wxWizardSizer::wxWizardSizer(wxWizard *owner)
             : m_owner(owner),
               m_childSize(wxDefaultSize)
{
}

wxSizerItem *wxWizardSizer::Insert(size_t index, wxSizerItem *item)
{
    m_owner->m_usingSizer = true;

    // We want to take account of the pages for the layout even when
    // they're hidden, so always add the corresponding flag.
    item->SetFlag(item->GetFlag() | wxRESERVE_SPACE_EVEN_IF_HIDDEN);

    return wxSizer::Insert(index, item);
}

void wxWizardSizer::RecalcSizes()
{
    // Effect of this function depends on m_owner->m_page and
    // it should be called whenever it changes (wxWizard::ShowPage)
    if ( m_owner->m_page )
    {
        m_owner->m_page->SetSize(wxRect(m_position, m_size));
    }
}

wxSize wxWizardSizer::CalcMin()
{
    return m_owner->GetPageSize();
}

wxSize wxWizardSizer::GetMaxChildSize()
{
    wxSize maxOfMin;

    for ( wxSizerItemList::compatibility_iterator childNode = m_children.GetFirst();
          childNode;
          childNode = childNode->GetNext() )
    {
        wxSizerItem *child = childNode->GetData();
        maxOfMin.IncTo(child->CalcMin());
        maxOfMin.IncTo(SiblingSize(child));
    }

    if ( m_owner->m_started )
    {
        m_childSize = maxOfMin;
    }

    return maxOfMin;
}

int wxWizardSizer::GetBorder() const
{
    return m_owner->m_border;
}

wxSize wxWizardSizer::SiblingSize(wxSizerItem *child)
{
    wxSize maxSibling;

    if ( child->IsWindow() )
    {
        wxWizardPage *page = wxDynamicCast(child->GetWindow(), wxWizardPage);
        if ( page )
        {
            for ( wxWizardPage *sibling = page->GetNext();
                  sibling;
                  sibling = sibling->GetNext() )
            {
                if ( sibling->GetSizer() )
                {
                    maxSibling.IncTo(sibling->GetSizer()->CalcMin());
                }
            }
        }
    }

    return maxSibling;
}

// ----------------------------------------------------------------------------
// generic wxWizard implementation
// ----------------------------------------------------------------------------

void wxWizard::Init()
{
    m_posWizard = wxDefaultPosition;
    m_page = nullptr;
    m_firstpage = nullptr;
    m_btnPrev = m_btnNext = nullptr;
    m_statbmp = nullptr;
    m_sizerBmpAndPage = nullptr;
    m_sizerPage = nullptr;
    m_border = 5;
    m_started = false;
    m_wasModal = false;
    m_usingSizer = false;
    m_bitmapBackgroundColour = *wxWHITE;
    m_bitmapPlacement = 0;
    m_bitmapMinimumWidth = 115;

    Bind(wxEVT_DPI_CHANGED, &wxWizard::WXHandleDPIChanged, this);
}

bool wxWizard::Create(wxWindow *parent,
                      int id,
                      const wxString& title,
                      const wxBitmapBundle& bitmap,
                      const wxPoint& pos,
                      long style)
{
    bool result = wxDialog::Create(parent,id,title,pos,wxDefaultSize,style);

    m_posWizard = pos;
    m_bitmap = bitmap ;

    DoCreateControls();

    return result;
}

wxWizard::~wxWizard()
{
    // normally we don't have to delete this sizer as it's deleted by the
    // associated window but if we never used it or didn't set it as the window
    // sizer yet, do delete it manually
    if ( !m_usingSizer || !m_started )
        delete m_sizerPage;
}

void wxWizard::AddBitmapRow(wxBoxSizer *mainColumn)
{
    m_sizerBmpAndPage = new wxBoxSizer(wxHORIZONTAL);
    mainColumn->Add(
        m_sizerBmpAndPage,
        1, // Vertically stretchable
        wxEXPAND // Horizontal stretching, no border
    );
    mainColumn->Add(0, FromDIP(5),
        0, // No vertical stretching
        wxEXPAND // No border, (mostly useless) horizontal stretching
    );

#if wxUSE_STATBMP
    if ( m_bitmap.IsOk() )
    {
        wxSize bitmapSize(wxDefaultSize);
        if (GetBitmapPlacement())
            bitmapSize.x = GetMinimumBitmapWidth();

        m_statbmp = new wxStaticBitmap(this, wxID_ANY, m_bitmap, wxDefaultPosition, bitmapSize);
        m_sizerBmpAndPage->Add(
            m_statbmp,
            0, // No horizontal stretching
            wxALL, // Border all around, top alignment
            FromDIP(5) // Border width
        );
        m_sizerBmpAndPage->Add(
            FromDIP(5), 0,
            0, // No horizontal stretching
            wxEXPAND // No border, (mostly useless) vertical stretching
        );
    }
#endif

    // Added to m_sizerBmpAndPage later
    m_sizerPage = new wxWizardSizer(this);
}

void wxWizard::AddStaticLine(wxBoxSizer *mainColumn)
{
#if wxUSE_STATLINE
    mainColumn->Add(
        new wxStaticLine(this, wxID_ANY),
        0, // Vertically unstretchable
        wxEXPAND | wxALL, // Border all around, horizontally stretchable
        FromDIP(5) // Border width
    );
    mainColumn->Add(0, FromDIP(5),
        0, // No vertical stretching
        wxEXPAND // No border, (mostly useless) horizontal stretching
    );
#else
    (void)mainColumn;
#endif // wxUSE_STATLINE
}

void wxWizard::AddBackNextPair(wxBoxSizer *buttonRow)
{
    wxASSERT_MSG( m_btnNext && m_btnPrev,
                  wxT("You must create the buttons before calling ")
                  wxT("wxWizard::AddBackNextPair") );

    wxBoxSizer *backNextPair = new wxBoxSizer(wxHORIZONTAL);
    buttonRow->Add(
        backNextPair,
        0, // No horizontal stretching
        wxALL, // Border all around
        FromDIP(5) // Border width
    );

    backNextPair->Add(m_btnPrev);
    backNextPair->Add(FromDIP(10), 0,
        0, // No horizontal stretching
        wxEXPAND // No border, (mostly useless) vertical stretching
    );
    backNextPair->Add(m_btnNext);
}

void wxWizard::AddButtonRow(wxBoxSizer *mainColumn)
{
    // the order in which the buttons are created determines the TAB order - at least under MSWindows...
    // although the 'back' button appears before the 'next' button, a more userfriendly tab order is
    // to activate the 'next' button first (create the next button before the back button).
    // The reason is: The user will repeatedly enter information in the wizard pages and then wants to
    // press 'next'. If a user uses mostly the keyboard, he would have to skip the 'back' button
    // every time. This is annoying. There is a second reason: RETURN acts as TAB. If the 'next'
    // button comes first in the TAB order, the user can enter information very fast using the RETURN
    // key to TAB to the next entry field and page. This would not be possible, if the 'back' button
    // was created before the 'next' button.

    bool isPda = (wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA);
    int buttonStyle = isPda ? wxBU_EXACTFIT : 0;

    wxBoxSizer *buttonRow = new wxBoxSizer(wxHORIZONTAL);
#ifdef __WXMAC__
    if (GetExtraStyle() & wxWIZARD_EX_HELPBUTTON)
        mainColumn->Add(
            buttonRow,
            0, // Vertically unstretchable
            wxEXPAND
            );
    else
#endif
    mainColumn->Add(
        buttonRow,
        0, // Vertically unstretchable
        wxALIGN_RIGHT // Right aligned, no border
    );

    // Desired TAB order is 'next', 'cancel', 'help', 'back'. This makes the 'back' button the last control on the page.
    // Create the buttons in the right order...
    wxButton *btnHelp=nullptr;
#ifdef __WXMAC__
    if (GetExtraStyle() & wxWIZARD_EX_HELPBUTTON)
        btnHelp=new wxButton(this, wxID_HELP, wxEmptyString, wxDefaultPosition, wxDefaultSize, buttonStyle);
#endif

    m_nextLabel = _("&Next >");
    m_finishLabel = _("&Finish");

    m_btnNext = new wxButton(this, wxID_FORWARD, m_nextLabel);
    wxButton *btnCancel=new wxButton(this, wxID_CANCEL, _("&Cancel"), wxDefaultPosition, wxDefaultSize, buttonStyle);
#ifndef __WXMAC__
    if (GetExtraStyle() & wxWIZARD_EX_HELPBUTTON)
        btnHelp=new wxButton(this, wxID_HELP, _("&Help"), wxDefaultPosition, wxDefaultSize, buttonStyle);
#endif
    m_btnPrev = new wxButton(this, wxID_BACKWARD, _("< &Back"), wxDefaultPosition, wxDefaultSize, buttonStyle);

    // compute the maximum width of the buttons and use it for all of them
    // (except for the "Help" button under Mac which is special there)
    wxSize buttonSize;
    buttonSize.IncTo(m_btnPrev->GetBestSize());
    buttonSize.IncTo(m_btnNext->GetBestSize());
    buttonSize.IncTo(btnCancel->GetBestSize());

    // use the other possible label for the "Next" button temporarily, so that
    // we could make it big enough to fit it too if it's longer
    m_btnNext->SetLabel(m_finishLabel);
    buttonSize.IncTo(m_btnNext->GetBestSize());

#ifndef __WXMAC__
    if (btnHelp)
        buttonSize.IncTo(btnHelp->GetBestSize());
#endif

    // now do make all buttons of the same (and big enough) size
    m_btnPrev->SetMinSize(buttonSize);
    m_btnNext->SetMinSize(buttonSize);
    btnCancel->SetMinSize(buttonSize);

#ifndef __WXMAC__
    if (btnHelp)
        btnHelp->SetMinSize(buttonSize);
#endif

    // restore the initial label of the 'next' button after temporarily
    // changing it above
    m_btnNext->SetLabel(m_nextLabel);

    if (btnHelp)
    {
        buttonRow->Add(
            btnHelp,
            0, // Horizontally unstretchable
            wxALL, // Border all around, top aligned
            FromDIP(5) // Border width
            );
#ifdef __WXMAC__
        // Put stretchable space between help button and others
        buttonRow->Add(0, 0, 1, wxALIGN_CENTRE, 0);
#endif
    }

    AddBackNextPair(buttonRow);

    buttonRow->Add(
        btnCancel,
        0, // Horizontally unstretchable
        wxALL, // Border all around, top aligned
        FromDIP(5) // Border width
    );
}

void wxWizard::DoCreateControls()
{
    // do nothing if the controls were already created
    if ( WasCreated() )
        return;

    bool isPda = (wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA);

    // Horizontal stretching, and if not PDA, border all around
    int mainColumnSizerFlags = isPda ? wxEXPAND : wxALL|wxEXPAND ;

    // wxWindow::SetSizer will be called at end
    wxBoxSizer *windowSizer = new wxBoxSizer(wxVERTICAL);

    wxBoxSizer *mainColumn = new wxBoxSizer(wxVERTICAL);
    windowSizer->Add(
        mainColumn,
        1, // Vertical stretching
        mainColumnSizerFlags,
        FromDIP(5) // Border width
    );

    AddBitmapRow(mainColumn);

    if (!isPda)
        AddStaticLine(mainColumn);

    AddButtonRow(mainColumn);

    SetSizer(windowSizer);
}

void wxWizard::SetPageSize(const wxSize& size)
{
    wxCHECK_RET(!m_started, wxT("wxWizard::SetPageSize after RunWizard"));
    m_sizePage = size;
}

void wxWizard::FitToPage(const wxWizardPage *page)
{
    wxCHECK_RET(!m_started, wxT("wxWizard::FitToPage after RunWizard"));

    while ( page )
    {
        wxSize size = page->GetBestSize();

        m_sizePage.IncTo(size);

        page = page->GetNext();
    }
}

bool wxWizard::ShowPage(wxWizardPage *page, bool goingForward)
{
    wxASSERT_MSG( page != m_page, wxT("this is useless") );

    wxSizerFlags flags(1);
    flags.Border(wxALL, m_border).Expand();

    if ( !m_started )
    {
        if ( m_usingSizer )
        {
            m_sizerBmpAndPage->Add(m_sizerPage, flags);
        }
    }


    // remember the old bitmap (if any) to compare with the new one later
    wxBitmap bmpPrev;

    // check for previous page
    if ( m_page )
    {
        // send the event to the old page
        wxWizardEvent event(wxEVT_WIZARD_PAGE_CHANGING, GetId(),
                            goingForward, m_page);
        if ( m_page->GetEventHandler()->ProcessEvent(event) &&
             !event.IsAllowed() )
        {
            // vetoed by the page
            return false;
        }

        m_page->Hide();

        bmpPrev = m_page->GetBitmap();

        if ( !m_usingSizer )
            m_sizerBmpAndPage->Detach(m_page);
    }

    // is this the end?
    if ( !page )
    {
        // terminate successfully
        if ( IsModal() )
        {
            EndModal(wxID_OK);
        }
        else
        {
            SetReturnCode(wxID_OK);
            Hide();
        }

        // and notify the user code (this is especially useful for modeless
        // wizards)
        wxWizardEvent event(wxEVT_WIZARD_FINISHED, GetId(), false, m_page);
        (void)GetEventHandler()->ProcessEvent(event);

        m_page = nullptr;

        return true;
    }

    // notice that we change m_page only here so that wxEVT_WIZARD_FINISHED
    // event above could still use the correct (i.e. old) value of m_page
    m_page = page;

    // position and show the new page
    (void)m_page->TransferDataToWindow();

    if ( m_usingSizer )
    {
        // wxWizardSizer::RecalcSizes wants to be called when m_page changes
        m_sizerPage->RecalcSizes();
    }
    else // pages are not managed by the sizer
    {
        m_sizerBmpAndPage->Add(m_page, flags);
        m_sizerBmpAndPage->SetItemMinSize(m_page, GetPageSize());
    }

#if wxUSE_STATBMP
    // update the bitmap if:it changed
    wxBitmap bmp;
    if ( m_statbmp )
    {
        bmp = m_page->GetBitmap();
        if ( !bmp.IsOk() )
            bmp = m_bitmap.GetBitmapFor(this);

        if ( !bmpPrev.IsOk() )
            bmpPrev = m_bitmap.GetBitmapFor(this);

        if (!GetBitmapPlacement())
        {
            if ( !bmp.IsSameAs(bmpPrev) )
                m_statbmp->SetBitmap(bmp);
        }
    }
#endif // wxUSE_STATBMP


    // and update the buttons state
    m_btnPrev->Enable(m_page != m_firstpage);

    const bool hasNext = HasNextPage(m_page);
    const wxString& label = hasNext ? m_nextLabel : m_finishLabel;
    if ( label != m_btnNext->GetLabel() )
        m_btnNext->SetLabel(label);

    m_btnNext->SetDefault();


    // send the change event to the new page now
    wxWizardEvent event(wxEVT_WIZARD_PAGE_CHANGED, GetId(), goingForward, m_page);
    (void)m_page->GetEventHandler()->ProcessEvent(event);

    // and finally show it
    m_page->Show();
    m_page->SetFocus();

    if ( !m_usingSizer )
        m_sizerBmpAndPage->Layout();

    if ( !m_started )
    {
        m_started = true;

        DoWizardLayout();
    }

    if (GetBitmapPlacement() && m_statbmp)
    {
        ResizeBitmap(bmp);

        if ( !bmp.IsSameAs(bmpPrev) )
            m_statbmp->SetBitmap(bmp);

        if (m_usingSizer)
            m_sizerPage->RecalcSizes();
    }

    wxWizardEvent pageShownEvent(wxEVT_WIZARD_PAGE_SHOWN, GetId(),
        goingForward, m_page);
    m_page->GetEventHandler()->ProcessEvent(pageShownEvent);

    return true;
}

/// Do fit, and adjust to screen size if necessary
void wxWizard::DoWizardLayout()
{
    if ( wxSystemSettings::GetScreenType() > wxSYS_SCREEN_PDA )
    {
        if (CanDoLayoutAdaptation())
            DoLayoutAdaptation();
        else
            GetSizer()->SetSizeHints(this);

        if ( m_posWizard == wxDefaultPosition )
            CentreOnScreen();
    }

    SetLayoutAdaptationDone(true);
}

bool wxWizard::RunWizard(wxWizardPage *firstPage)
{
    wxCHECK_MSG( firstPage, false, wxT("can't run empty wizard") );

    m_firstpage = firstPage;

    // can't return false here because there is no old page
    (void)ShowPage(firstPage, true /* forward */);

    m_wasModal = true;

    return ShowModal() == wxID_OK;
}

wxWizardPage *wxWizard::GetCurrentPage() const
{
    return m_page;
}

wxSize wxWizard::GetPageSize() const
{
    // default width and height of the page
    int DEFAULT_PAGE_WIDTH,
        DEFAULT_PAGE_HEIGHT;
    if ( wxSystemSettings::GetScreenType() <= wxSYS_SCREEN_PDA )
    {
        // Make the default page size small enough to fit on screen
        DEFAULT_PAGE_WIDTH = wxSystemSettings::GetMetric(wxSYS_SCREEN_X, m_parent) / 2;
        DEFAULT_PAGE_HEIGHT = wxSystemSettings::GetMetric(wxSYS_SCREEN_Y, m_parent) / 2;
    }
    else // !PDA
    {
        DEFAULT_PAGE_WIDTH =
        DEFAULT_PAGE_HEIGHT = 270;
    }

    // start with default minimal size
    wxSize pageSize(DEFAULT_PAGE_WIDTH, DEFAULT_PAGE_HEIGHT);

    // make the page at least as big as specified by user
    pageSize.IncTo(m_sizePage);

    if ( m_statbmp )
    {
        // make the page at least as tall as the bitmap
        pageSize.IncTo(wxSize(0, m_bitmap.GetPreferredLogicalSizeFor(this).GetHeight()));
    }

    if ( m_usingSizer )
    {
        // make it big enough to contain all pages added to the sizer
        pageSize.IncTo(m_sizerPage->GetMaxChildSize());
    }

    return pageSize;
}

wxSizer *wxWizard::GetPageAreaSizer() const
{
    return m_sizerPage;
}

void wxWizard::SetBorder(int border)
{
    wxCHECK_RET(!m_started, wxT("wxWizard::SetBorder after RunWizard"));

    m_border = border;
}

void wxWizard::OnCancel(wxCommandEvent& WXUNUSED(eventUnused))
{
    // this function probably can never be called when we don't have an active
    // page, but a small extra check won't hurt
    wxWindow *win = m_page ? (wxWindow *)m_page : (wxWindow *)this;

    wxWizardEvent event(wxEVT_WIZARD_CANCEL, GetId(), false, m_page);
    if ( !win->GetEventHandler()->ProcessEvent(event) || event.IsAllowed() )
    {
        // no objections - close the dialog
        if(IsModal())
        {
            EndModal(wxID_CANCEL);
        }
        else
        {
            SetReturnCode(wxID_CANCEL);
            Hide();
        }
    }
    //else: request to Cancel ignored
}

void wxWizard::OnBackOrNext(wxCommandEvent& event)
{
    wxASSERT_MSG( (event.GetEventObject() == m_btnNext) ||
                  (event.GetEventObject() == m_btnPrev),
                  wxT("unknown button") );

    wxCHECK_RET( m_page, wxT("should have a valid current page") );

    // ask the current page first: notice that we do it before calling
    // GetNext/Prev() because the data transferred from the controls of the page
    // may change the value returned by these methods
    if ( !m_page->Validate() || !m_page->TransferDataFromWindow() )
    {
        // the page data is incorrect, don't do anything
        return;
    }

    bool forward = event.GetEventObject() == m_btnNext;

    // Give the application a chance to set state which may influence GetNext()/GetPrev()
    wxWizardEvent eventPreChanged(wxEVT_WIZARD_BEFORE_PAGE_CHANGED, GetId(), forward, m_page);
    (void)m_page->GetEventHandler()->ProcessEvent(eventPreChanged);

    if (!eventPreChanged.IsAllowed())
        return;

    wxWizardPage *page;
    if ( forward )
    {
        page = m_page->GetNext();
    }
    else // back
    {
        page = m_page->GetPrev();

        wxASSERT_MSG( page, wxT("\"<Back\" button should have been disabled") );
    }

    // just pass to the new page (or maybe not - but we don't care here)
    (void)ShowPage(page, forward);
}

void wxWizard::OnHelp(wxCommandEvent& WXUNUSED(event))
{
    // this function probably can never be called when we don't have an active
    // page, but a small extra check won't hurt
    if(m_page != nullptr)
    {
        // Create and send the help event to the specific page handler
        // event data contains the active page so that context-sensitive
        // help is possible
        wxWizardEvent eventHelp(wxEVT_WIZARD_HELP, GetId(), true, m_page);
        (void)m_page->GetEventHandler()->ProcessEvent(eventHelp);
    }
}

void wxWizard::OnWizEvent(wxWizardEvent& event)
{
    // the dialogs have wxWS_EX_BLOCK_EVENTS style on by default but we want to
    // propagate wxEVT_WIZARD_XXX to the parent (if any), so do it manually
    if ( !(GetExtraStyle() & wxWS_EX_BLOCK_EVENTS) )
    {
        // the event will be propagated anyhow
        event.Skip();
    }
    else
    {
        wxWindow *parent = GetParent();

        if ( !parent || !parent->GetEventHandler()->ProcessEvent(event) )
        {
            event.Skip();
        }
    }

    if ( ( !m_wasModal ) &&
         event.IsAllowed() &&
         ( event.GetEventType() == wxEVT_WIZARD_FINISHED ||
           event.GetEventType() == wxEVT_WIZARD_CANCEL
         )
       )
    {
        Destroy();
    }
}

void wxWizard::SetBitmap(const wxBitmapBundle& bitmap)
{
    m_bitmap = bitmap;
    if (m_statbmp)
        m_statbmp->SetBitmap(m_bitmap);
}

void wxWizard::WXHandleDPIChanged(wxDPIChangedEvent& event)
{
    if ( m_statbmp )
    {
        wxBitmap bmp;
        if ( m_page )
            bmp = m_page->GetBitmap();
        if ( !bmp.IsOk() )
            bmp = m_bitmap.GetBitmapFor(this);

        ResizeBitmap(bmp);
        m_statbmp->SetBitmap(bmp);
    }

    event.Skip();
}

// ----------------------------------------------------------------------------
// wxWizardEvent
// ----------------------------------------------------------------------------

wxWizardEvent::wxWizardEvent(wxEventType type, int id, bool direction, wxWizardPage* page)
             : wxNotifyEvent(type, id)
{
    // Modified 10-20-2001 Robert Cavanaugh
    // add the active page to the event data
    m_direction = direction;
    m_page = page;
}

/// Do the adaptation
bool wxWizard::DoLayoutAdaptation()
{
    wxWindowList windows;
    wxWindowList pages;

    // Make all the pages (that use sizers) scrollable
    for ( wxSizerItemList::compatibility_iterator node = m_sizerPage->GetChildren().GetFirst(); node; node = node->GetNext() )
    {
        wxSizerItem * const item = node->GetData();
        if ( item->IsWindow() )
        {
            wxWizardPage* page = wxDynamicCast(item->GetWindow(), wxWizardPage);
            if (page)
            {
                while (page)
                {
                    if (!pages.Find(page) && page->GetSizer())
                    {
                        // Create a scrolled window and reparent
                        wxScrolledWindow* scrolledWindow = new wxScrolledWindow(page, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL|wxVSCROLL|wxHSCROLL|wxBORDER_NONE);
                        wxSizer* oldSizer = page->GetSizer();

                        wxSizer* newSizer = new wxBoxSizer(wxVERTICAL);
                        newSizer->Add(scrolledWindow,1, wxEXPAND, 0);

                        page->SetSizer(newSizer, false /* don't delete the old sizer */);

                        scrolledWindow->SetSizer(oldSizer);

                        wxStandardDialogLayoutAdapter::DoReparentControls(page, scrolledWindow);

                        pages.Append(page);
                        windows.Append(scrolledWindow);
                    }
                    page = page->GetNext();
                }
            }
        }
    }

    wxStandardDialogLayoutAdapter::DoFitWithScrolling(this, windows);

    // Size event doesn't get sent soon enough on wxGTK
    Layout();

    SetLayoutAdaptationDone(true);

    return true;
}

bool wxWizard::ResizeBitmap(wxBitmap& bmp)
{
    if (!GetBitmapPlacement())
        return false;

    if (bmp.IsOk())
    {
        wxSize pageSize = m_sizerPage->GetSize();
        if (pageSize == wxSize(0,0))
            pageSize = GetPageSize();
        int bitmapWidth = wxMax(bmp.GetLogicalWidth(), GetMinimumBitmapWidth());
        int bitmapHeight = pageSize.y;

        if (!m_statbmp->GetBitmap().IsOk() || m_statbmp->GetBitmap().GetLogicalHeight() != bitmapHeight)
        {
            wxBitmap bitmap;
            bitmap.CreateWithLogicalSize(bitmapWidth, bitmapHeight, bmp.GetScaleFactor(), bmp.GetDepth());
            {
                wxMemoryDC dc;
                dc.SelectObject(bitmap);
                dc.SetBackground(wxBrush(m_bitmapBackgroundColour));
                dc.Clear();

                if (GetBitmapPlacement() & wxWIZARD_TILE)
                {
                    TileBitmap(wxRect(0, 0, bitmapWidth, bitmapHeight), dc, bmp);
                }
                else
                {
                    int x, y;

                    if (GetBitmapPlacement() & wxWIZARD_HALIGN_LEFT)
                        x = 0;
                    else if (GetBitmapPlacement() & wxWIZARD_HALIGN_RIGHT)
                        x = bitmapWidth - bmp.GetLogicalWidth();
                    else
                        x = (bitmapWidth - bmp.GetLogicalWidth())/2;

                    if (GetBitmapPlacement() & wxWIZARD_VALIGN_TOP)
                        y = 0;
                    else if (GetBitmapPlacement() & wxWIZARD_VALIGN_BOTTOM)
                        y = bitmapHeight - bmp.GetLogicalHeight();
                    else
                        y = (bitmapHeight - bmp.GetLogicalHeight())/2;

                    dc.DrawBitmap(bmp, x, y, true);
                    dc.SelectObject(wxNullBitmap);
                }
            }

            bmp = bitmap;
        }
    }

    return true;
}

bool wxWizard::TileBitmap(const wxRect& rect, wxDC& dc, const wxBitmap& bitmap)
{
    int w = bitmap.GetLogicalWidth();
    int h = bitmap.GetLogicalHeight();

    wxMemoryDC dcMem;

    dcMem.SelectObjectAsSource(bitmap);

    int i, j;
    for (i = rect.x; i < rect.x + rect.width; i += w)
    {
        for (j = rect.y; j < rect.y + rect.height; j+= h)
            dc.Blit(i, j, bitmap.GetLogicalWidth(), bitmap.GetLogicalHeight(), & dcMem, 0, 0);
    }
    dcMem.SelectObject(wxNullBitmap);

    return true;
}

#endif // wxUSE_WIZARDDLG
