///////////////////////////////////////////////////////////////////////////////
//
// wxFormBuilder - A Visual Dialog Editor for wxWidgets.
// Copyright (C) 2005 José Antonio Hurtado
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
// Written by
//   José Antonio Hurtado - joseantonio.hurtado@gmail.com
//   Juan Antonio Ortega  - jortegalalmolda@gmail.com
//
///////////////////////////////////////////////////////////////////////////////

#include <wx/splitter.h>

#include <common/xmlutils.h>
#include <plugin_interface/plugin.h>
#include <plugin_interface/xrcconv.h>

#include "bookutils.h"

#ifndef __WXGTK__
    #include <wx/listctrl.h>
#endif


class wxCustomSplitterWindow : public wxSplitterWindow
{
public:
    wxCustomSplitterWindow(
      wxWindow* parent, wxWindowID id, const wxPoint& point = wxDefaultPosition, const wxSize& size = wxDefaultSize,
      long style = wxSP_3D) :
      wxSplitterWindow(parent, id, point, size, style), m_customSashPos(0), m_customMinPaneSize(0)
    {
    }

    int m_customSashPos;
    int m_customMinPaneSize;
    int m_initialSashPos;

    // Used to ensure sash position is correct
    void OnIdle(wxIdleEvent&)
    {
        Disconnect(wxEVT_IDLE, wxIdleEventHandler(wxCustomSplitterWindow::OnIdle));

        // So the selection of the sizer at its initial position is cleared, then shown at the correct position
        Freeze();
        SetSashPosition(m_initialSashPos);
        Layout();
        Refresh();
        Update();
        Thaw();
    }

private:
    bool OnSashPositionChange(int newSashPosition) override
    {
        m_customSashPos = newSashPosition;
        return wxSplitterWindow::OnSashPositionChange(newSashPosition);
    }

    void OnDoubleClickSash(int, int) override
    {
        if (0 == m_customMinPaneSize) {
            wxMessageBox(
              wxT(
                "Double-clicking a wxSplitterWindow sash with the minimum pane size set to 0 would normally unsplit "
                "it.\nHowever, it is difficult to design a pane that has been closed, so this action has been vetoed."),
              wxT("Unsplit Vetoed!"), wxICON_INFORMATION, NULL);
        }
    }
};

// Since wxGTK 2.8, wxNotebook has been sending page changed events in its destructor - this causes strange behavior
#if defined(__WXGTK__)
class wxCustomNotebook : public wxNotebook
{
public:
    wxCustomNotebook(
      wxWindow* parent, wxWindowID id, const wxPoint& point = wxDefaultPosition, const wxSize& size = wxDefaultSize,
      long style = 0) :
      wxNotebook(parent, id, point, size, style)
    {
    }

    ~wxCustomNotebook() override
    {
        while (GetEventHandler() != this) {
            // Remove and delete extra event handlers
            PopEventHandler(true);
        }
    }
};
#else
typedef wxNotebook wxCustomNotebook;
#endif

///////////////////////////////////////////////////////////////////////////////

/// Event handler for events generated by controls in this plugin
class ComponentEvtHandler : public wxEvtHandler
{
private:
    wxWindow* m_window;
    IManager* m_manager;

public:
    ComponentEvtHandler(wxWindow* win, IManager* manager) : m_window(win), m_manager(manager) {}

protected:
#ifdef wxUSE_COLLPANE
    void OnCollapsiblePaneChanged([[maybe_unused]] wxCollapsiblePaneEvent& event)
    {
        wxCollapsiblePane* collpane = wxDynamicCast(m_window, wxCollapsiblePane);
        if (collpane != NULL) {
            wxString s = (event.GetCollapsed()) ? wxT("1") : wxT("0");
            m_manager->ModifyProperty(collpane, _("collapsed"), s);
            collpane->SetFocus();
        }

        event.Skip();
    }
#endif

    void OnNotebookPageChanged([[maybe_unused]] wxNotebookEvent& event)
    {
        OnBookPageChanged<wxNotebook>(event.GetSelection(), &event);
        event.Skip();
    }

    void OnListbookPageChanged([[maybe_unused]] wxListbookEvent& event)
    {
        OnBookPageChanged<wxListbook>(event.GetSelection(), &event);
        event.Skip();
    }

    void OnChoicebookPageChanged([[maybe_unused]] wxChoicebookEvent& event)
    {
        OnBookPageChanged<wxChoicebook>(event.GetSelection(), &event);
        event.Skip();
    }

    void OnAuiNotebookPageChanged([[maybe_unused]] wxAuiNotebookEvent& event)
    {
        OnBookPageChanged<wxAuiNotebook>(event.GetSelection(), &event);
        event.Skip();
    }

    void OnSplitterSashChanged([[maybe_unused]] wxSplitterEvent& event)
    {
        wxCustomSplitterWindow* window = wxDynamicCast(m_window, wxCustomSplitterWindow);
        if (window != NULL) {
            if (window->m_customSashPos != 0) {
                m_manager->ModifyProperty(window, _("sashpos"), wxString::Format(wxT("%i"), window->GetSashPosition()));
            }
        }
    }

    template <class T>
    void OnBookPageChanged(int selPage, [[maybe_unused]] wxEvent* event)
    {
        // Only handle events from this book - prevents problems with nested books, because OnSelected is fired on an
        // object and all of its parents
        if (m_window != event->GetEventObject()) {
            return;
        }

        if (selPage < 0) {
            return;
        }

        size_t count = m_manager->GetChildCount(m_window);
        for (size_t i = 0; i < count; i++) {
            wxObject* wxChild = m_manager->GetChild(m_window, i);
            IObject* iChild = m_manager->GetIObject(wxChild);
            if (iChild) {
                if ((int)i == selPage && !iChild->GetPropertyAsInteger(_("select"))) {
                    m_manager->ModifyProperty(wxChild, _("select"), wxT("1"), false);
                } else if ((int)i != selPage && iChild->GetPropertyAsInteger(_("select"))) {
                    m_manager->ModifyProperty(wxChild, _("select"), wxT("0"), false);
                }
            }
        }

        // Select the corresponding panel in the object tree
        T* book = wxDynamicCast(m_window, T);
        if (NULL != book) {
            m_manager->SelectObject(book->GetPage(selPage));
        }
    }

    void OnAuiNotebookPageClosed([[maybe_unused]] wxAuiNotebookEvent& event)
    {
        wxMessageBox(
          wxT("wxAuiNotebook pages can normally be closed.\nHowever, it is difficult to design a page that has been "
              "closed, so this action has been vetoed."),
          wxT("Page Close Vetoed!"), wxICON_INFORMATION, NULL);
        event.Veto();
    }

    void OnAuiNotebookAllowDND([[maybe_unused]] wxAuiNotebookEvent& event)
    {
        wxMessageBox(
          wxT("wxAuiNotebook pages can be dragged to other wxAuiNotebooks if the wxEVT_COMMAND_AUINOTEBOOK_ALLOW_DND "
              "event is caught and allowed.\nHowever, it is difficult to design a page that has been moved, so this "
              "action was not allowed."),
          wxT("Page Move Not Allowed!"), wxICON_INFORMATION, NULL);
        event.Veto();
    }

    wxDECLARE_EVENT_TABLE();
};

wxBEGIN_EVENT_TABLE(ComponentEvtHandler, wxEvtHandler)
#ifdef wxUSE_COLLPANE
EVT_COLLAPSIBLEPANE_CHANGED(wxID_ANY, ComponentEvtHandler::OnCollapsiblePaneChanged)
#endif
EVT_NOTEBOOK_PAGE_CHANGED(wxID_ANY, ComponentEvtHandler::OnNotebookPageChanged)
EVT_LISTBOOK_PAGE_CHANGED(wxID_ANY, ComponentEvtHandler::OnListbookPageChanged)
EVT_CHOICEBOOK_PAGE_CHANGED(wxID_ANY, ComponentEvtHandler::OnChoicebookPageChanged)
EVT_AUINOTEBOOK_PAGE_CHANGED(wxID_ANY, ComponentEvtHandler::OnAuiNotebookPageChanged)
EVT_AUINOTEBOOK_PAGE_CLOSE(wxID_ANY, ComponentEvtHandler::OnAuiNotebookPageClosed)
EVT_AUINOTEBOOK_ALLOW_DND(wxID_ANY, ComponentEvtHandler::OnAuiNotebookAllowDND)
EVT_SPLITTER_SASH_POS_CHANGED(wxID_ANY, ComponentEvtHandler::OnSplitterSashChanged)
wxEND_EVENT_TABLE()

///////////////////////////////////////////////////////////////////////////////

class PanelComponent : public ComponentBase
{
public:
    wxObject* Create(IObject* obj, wxObject* parent) override
    {
        wxPanel* panel = new wxPanel(
          (wxWindow*)parent, wxID_ANY, obj->GetPropertyAsPoint(_("pos")), obj->GetPropertyAsSize(_("size")),
          obj->GetPropertyAsInteger(_("style")) | obj->GetPropertyAsInteger(_("window_style")));
        return panel;
    }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj);
        filter.AddWindowProperties();
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc);
        filter.AddWindowProperties();
        return xfb;
    }
};

#ifdef wxUSE_COLLPANE
class CollapsiblePaneComponent : public ComponentBase
{
public:
    wxObject* Create(IObject* obj, wxObject* parent) override
    {
        wxCollapsiblePane* collpane = new wxCollapsiblePane(
          (wxWindow*)parent, wxID_ANY, obj->GetPropertyAsString(_("label")), obj->GetPropertyAsPoint(_("pos")),
          obj->GetPropertyAsSize(_("size")),
          obj->GetPropertyAsInteger(_("style")) | obj->GetPropertyAsInteger(_("window_style")));

        collpane->Collapse(obj->GetPropertyAsInteger(_("collapsed")) != 0);

        collpane->PushEventHandler(new ComponentEvtHandler(collpane, GetManager()));

        return collpane;
    }

    void Cleanup(wxObject* obj) override
    {
        auto* window = wxDynamicCast(obj, wxCollapsiblePane);
        if (window) {
            window->PopEventHandler(true);
        }
    }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj);
        filter.AddWindowProperties();
        filter.AddProperty(XrcFilter::Type::Text, "label");
        filter.AddProperty(XrcFilter::Type::Bool, "collapsed");
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc);
        filter.AddWindowProperties();
        filter.AddProperty(XrcFilter::Type::Text, "label");
        filter.AddProperty(XrcFilter::Type::Bool, "collapsed");
        return xfb;
    }
};
#endif  // wxUSE_COLLPANE

class SplitterWindowComponent : public ComponentBase
{
    wxObject* Create(IObject* obj, wxObject* parent) override
    {
        wxCustomSplitterWindow* splitter = new wxCustomSplitterWindow(
          (wxWindow*)parent, wxID_ANY, obj->GetPropertyAsPoint(_("pos")), obj->GetPropertyAsSize(_("size")),
          (obj->GetPropertyAsInteger(_("style")) | obj->GetPropertyAsInteger(_("window_style"))) &
            ~wxSP_PERMIT_UNSPLIT);

        if (!obj->IsPropertyNull(_("sashgravity"))) {
            float gravity = obj->GetPropertyAsFloat(_("sashgravity"));
            gravity = (gravity < 0.0 ? 0.0 : gravity);
            gravity = (gravity > 1.0 ? 1.0 : gravity);
            splitter->SetSashGravity(gravity);
        }
        if (!obj->IsPropertyNull(_("min_pane_size"))) {
            int minPaneSize = obj->GetPropertyAsInteger(_("min_pane_size"));
            splitter->m_customMinPaneSize = minPaneSize;
            minPaneSize = (minPaneSize < 1 ? 1 : minPaneSize);
            splitter->SetMinimumPaneSize(minPaneSize);
        }

        // Always have a child so it is drawn consistently
        splitter->Initialize(new wxPanel(splitter));

        // Used to ensure sash position is correct
        splitter->m_initialSashPos = obj->GetPropertyAsInteger(_("sashpos"));
        splitter->Connect(wxEVT_IDLE, wxIdleEventHandler(wxCustomSplitterWindow::OnIdle));

        return splitter;
    }

    void Cleanup(wxObject* obj) override
    {
        // The derived class doesn't implement wxWidgets RTTI so cast to its base class
        auto* window = wxDynamicCast(obj, wxSplitterWindow);
        if (window) {
            // Because of possible error conditions the handler might not have been pushed
            auto* compHandler = dynamic_cast<ComponentEvtHandler*>(window->GetEventHandler());
            if (compHandler) {
                window->PopEventHandler(true);
            }
        }
    }

    void OnCreated(wxObject* wxobject, wxWindow* /*wxparent*/) override
    {
        wxCustomSplitterWindow* splitter = wxDynamicCast(wxobject, wxCustomSplitterWindow);
        if (NULL == splitter) {
            wxLogError(_("This should be a wxSplitterWindow"));
            return;
        }

        // Remove default panel
        wxWindow* firstChild = splitter->GetWindow1();

        size_t childCount = GetManager()->GetChildCount(wxobject);
        switch (childCount) {
            case 1: {
                // The child should be a splitteritem
                wxObject* splitterItem = GetManager()->GetChild(wxobject, 0);

                // This one should be the actual wxWindow
                wxWindow* subwindow = wxDynamicCast(GetManager()->GetChild(splitterItem, 0), wxWindow);
                if (NULL == subwindow) {
                    wxLogError(_("A SplitterItem is abstract and must have a child!"));
                    return;
                }

                if (firstChild) {
                    splitter->ReplaceWindow(firstChild, subwindow);
                    firstChild->Destroy();
                } else {
                    splitter->Initialize(subwindow);
                }
                splitter->PushEventHandler(new ComponentEvtHandler(splitter, GetManager()));
                break;
            }
            case 2: {
                // The child should be a splitteritem
                wxObject* splitterItem0 = GetManager()->GetChild(wxobject, 0);
                wxObject* splitterItem1 = GetManager()->GetChild(wxobject, 1);

                // This one should be the actual wxWindow
                wxWindow* subwindow0 = wxDynamicCast(GetManager()->GetChild(splitterItem0, 0), wxWindow);
                wxWindow* subwindow1 = wxDynamicCast(GetManager()->GetChild(splitterItem1, 0), wxWindow);

                if (NULL == subwindow0 || NULL == subwindow1) {
                    wxLogError(_("A SplitterItem is abstract and must have a child!"));
                    return;
                }

                // Get the split mode and sash position
                IObject* obj = GetManager()->GetIObject(wxobject);
                if (obj == NULL) {
                    return;
                }

                int sashPos = obj->GetPropertyAsInteger(_("sashpos"));
                int splitmode = obj->GetPropertyAsInteger(_("splitmode"));

                if (firstChild) {
                    splitter->ReplaceWindow(firstChild, subwindow0);
                    firstChild->Destroy();
                }

                if (splitmode == wxSPLIT_VERTICAL) {
                    splitter->SplitVertically(subwindow0, subwindow1, sashPos);
                } else {
                    splitter->SplitHorizontally(subwindow0, subwindow1, sashPos);
                }

                splitter->PushEventHandler(new ComponentEvtHandler(splitter, GetManager()));
                break;
            }
            default:
                return;
        }
    }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj);
        filter.AddWindowProperties();
        filter.AddProperty(XrcFilter::Type::Integer, "sashpos");
        filter.AddProperty(XrcFilter::Type::Float, "sashgravity", "gravity");
        filter.AddProperty(XrcFilter::Type::Integer, "min_pane_size", "minsize");
        if (obj->GetPropertyAsString("splitmode") == "wxSPLIT_VERTICAL") {
            filter.AddPropertyValue("orientation", "vertical");
        } else {
            filter.AddPropertyValue("orientation", "horizontal");
        }
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc);
        filter.AddWindowProperties();
        filter.AddProperty(XrcFilter::Type::Integer, "sashpos");
        filter.AddProperty(XrcFilter::Type::Float, "gravity", "sashgravity");
        filter.AddProperty(XrcFilter::Type::Integer, "minsize", "min_pane_size");
        if (const auto* orientationElement = xrc->FirstChildElement("orientation")) {
            if (XMLUtils::GetText(orientationElement) == "vertical") {
                filter.AddPropertyValue("splitmode", "wxSPLIT_VERTICAL");
            } else {
                filter.AddPropertyValue("splitmode", "wxSPLIT_HORIZONTAL");
            }
        }
        return xfb;
    }
};

class SplitterItemComponent : public ComponentBase
{
    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        // FIXME: Why is this __dummyitem__ required?
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj, "__dummyitem__", "");
        return xrc;
    }
};

class ScrolledWindowComponent : public ComponentBase
{
public:
    wxObject* Create(IObject* obj, wxObject* parent) override
    {
        wxScrolledWindow* sw = new wxScrolledWindow(
          (wxWindow*)parent, wxID_ANY, obj->GetPropertyAsPoint(_("pos")), obj->GetPropertyAsSize(_("size")),
          obj->GetPropertyAsInteger(_("style")) | obj->GetPropertyAsInteger(_("window_style")));

        sw->SetScrollRate(obj->GetPropertyAsInteger(_("scroll_rate_x")), obj->GetPropertyAsInteger(_("scroll_rate_y")));
        return sw;
    }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj);
        filter.AddWindowProperties();
        filter.AddPropertyPair("scroll_rate_x", "scroll_rate_y", "scrollrate");
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc);
        filter.AddWindowProperties();
        filter.AddPropertyPair("scrollrate", "scroll_rate_x", "scroll_rate_y");
        return xfb;
    }
};

class NotebookComponent : public ComponentBase
{
public:
    wxObject* Create(IObject* obj, wxObject* parent) override
    {
        wxNotebook* book = new wxCustomNotebook(
          (wxWindow*)parent, wxID_ANY, obj->GetPropertyAsPoint(_("pos")), obj->GetPropertyAsSize(_("size")),
          obj->GetPropertyAsInteger(_("style")) | obj->GetPropertyAsInteger(_("window_style")));

        BookUtils::AddImageList(obj, book);

        book->PushEventHandler(new ComponentEvtHandler(book, GetManager()));

        return book;
    }

    void Cleanup(wxObject* obj) override
    {
        auto* window = wxDynamicCast(obj, wxNotebook);
        if (window) {
            window->PopEventHandler(true);
        }
    }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj);
        filter.AddWindowProperties();
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc);
        filter.AddWindowProperties();
        return xfb;
    }
};

class NotebookPageComponent : public ComponentBase
{
public:
    void OnCreated(wxObject* wxobject, wxWindow* wxparent) override
    {
        BookUtils::OnCreated<wxNotebook>(wxobject, wxparent, GetManager(), _("NotebookPageComponent"));
    }

    void OnSelected(wxObject* wxobject) override { BookUtils::OnSelected<wxNotebook>(wxobject, GetManager()); }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj, std::nullopt, "");
        filter.AddProperty(XrcFilter::Type::Text, "label");
        filter.AddProperty(XrcFilter::Type::Bool, "select", "selected");
        if (!obj->IsPropertyNull("bitmap")) {
            filter.AddProperty(XrcFilter::Type::Bitmap, "bitmap");
        }
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc, std::nullopt, "");
        filter.AddProperty(XrcFilter::Type::Text, "label");
        filter.AddProperty(XrcFilter::Type::Bool, "selected", "select");
        filter.AddProperty(XrcFilter::Type::Bitmap, "bitmap");
        return xfb;
    }
};

class ListbookComponent : public ComponentBase
{
public:
    wxObject* Create(IObject* obj, wxObject* parent) override
    {
        wxListbook* book = new wxListbook(
          (wxWindow*)parent, wxID_ANY, obj->GetPropertyAsPoint(_("pos")), obj->GetPropertyAsSize(_("size")),
          obj->GetPropertyAsInteger(_("style")) | obj->GetPropertyAsInteger(_("window_style")));

        BookUtils::AddImageList(obj, book);

        book->PushEventHandler(new ComponentEvtHandler(book, GetManager()));

        return book;
    }

    void Cleanup(wxObject* obj) override
    {
        auto* window = wxDynamicCast(obj, wxListbook);
        if (window) {
            window->PopEventHandler(true);
        }
    }

// Small icon style not supported by GTK
#ifndef __WXGTK__
    void OnCreated(wxObject* wxobject, wxWindow* wxparent) override
    {
        wxListbook* book = wxDynamicCast(wxparent, wxListbook);
        if (book) {
            // Small icon style if bitmapsize is not set
            IObject* obj = GetManager()->GetIObject(wxobject);
            if (obj->GetPropertyAsString(_("bitmapsize")).empty()) {
                wxListView* tmpListView = book->GetListView();
                long flags = tmpListView->GetWindowStyleFlag();
                flags = (flags & ~wxLC_ICON) | wxLC_SMALL_ICON;
                tmpListView->SetWindowStyleFlag(flags);
            }
        }
    }
#endif

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj);
        filter.AddWindowProperties();
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc);
        filter.AddWindowProperties();
        return xfb;
    }
};

class ListbookPageComponent : public ComponentBase
{
public:
    void OnCreated(wxObject* wxobject, wxWindow* wxparent) override
    {
        BookUtils::OnCreated<wxListbook>(wxobject, wxparent, GetManager(), _("ListbookPageComponent"));
    }

    void OnSelected(wxObject* wxobject) override { BookUtils::OnSelected<wxListbook>(wxobject, GetManager()); }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj, std::nullopt, "");
        filter.AddProperty(XrcFilter::Type::Text, "label");
        filter.AddProperty(XrcFilter::Type::Bool, "select", "selected");
        if (!obj->IsPropertyNull("bitmap")) {
            filter.AddProperty(XrcFilter::Type::Bitmap, "bitmap");
        }
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc, std::nullopt, "");
        filter.AddProperty(XrcFilter::Type::Text, "label");
        filter.AddProperty(XrcFilter::Type::Bool, "selected", "select");
        filter.AddProperty(XrcFilter::Type::Bitmap, "bitmap");
        return xfb;
    }
};

class ChoicebookComponent : public ComponentBase
{
public:
    wxObject* Create(IObject* obj, wxObject* parent) override
    {
        wxChoicebook* book = new wxChoicebook(
          (wxWindow*)parent, wxID_ANY, obj->GetPropertyAsPoint(_("pos")), obj->GetPropertyAsSize(_("size")),
          obj->GetPropertyAsInteger(_("style")) | obj->GetPropertyAsInteger(_("window_style")));

        book->PushEventHandler(new ComponentEvtHandler(book, GetManager()));

        return book;
    }

    void Cleanup(wxObject* obj) override
    {
        auto* window = wxDynamicCast(obj, wxChoicebook);
        if (window) {
            window->PopEventHandler(true);
        }
    }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj);
        filter.AddWindowProperties();
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc);
        filter.AddWindowProperties();
        return xfb;
    }
};

class ChoicebookPageComponent : public ComponentBase
{
public:
    void OnCreated(wxObject* wxobject, wxWindow* wxparent) override
    {
        BookUtils::OnCreated<wxChoicebook>(wxobject, wxparent, GetManager(), _("ChoicebookPageComponent"));
    }

    void OnSelected(wxObject* wxobject) override { BookUtils::OnSelected<wxChoicebook>(wxobject, GetManager()); }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj, std::nullopt, "");
        filter.AddProperty(XrcFilter::Type::Text, "label");
        filter.AddProperty(XrcFilter::Type::Bool, "select", "selected");
        // FIXME: bitmap currently missing in data model
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc, std::nullopt, "");
        filter.AddProperty(XrcFilter::Type::Text, "label");
        filter.AddProperty(XrcFilter::Type::Bool, "selected", "select");
        // FIXME: bitmap currently missing in data model
        return xfb;
    }
};

class AuiNotebookComponent : public ComponentBase
{
public:
    wxObject* Create(IObject* obj, wxObject* parent) override
    {
        wxAuiNotebook* book = new wxAuiNotebook(
          (wxWindow*)parent, wxID_ANY, obj->GetPropertyAsPoint(_("pos")), obj->GetPropertyAsSize(_("size")),
          obj->GetPropertyAsInteger(_("style")) | obj->GetPropertyAsInteger(_("window_style")));

        book->SetTabCtrlHeight(obj->GetPropertyAsInteger(_("tab_ctrl_height")));
        book->SetUniformBitmapSize(obj->GetPropertyAsSize(_("uniform_bitmap_size")));

        book->PushEventHandler(new ComponentEvtHandler(book, GetManager()));

        return book;
    }

    void Cleanup(wxObject* obj) override
    {
        auto* window = wxDynamicCast(obj, wxAuiNotebook);
        if (window) {
            window->PopEventHandler(true);
        }
    }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj);
        filter.AddWindowProperties();
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc);
        filter.AddWindowProperties();
        return xfb;
    }
};

class AuiNotebookPageComponent : public ComponentBase
{
public:
    void OnCreated(wxObject* wxobject, wxWindow* wxparent) override
    {
        // Easy read-only property access
        IObject* obj = GetManager()->GetIObject(wxobject);

        wxAuiNotebook* book = wxDynamicCast(wxparent, wxAuiNotebook);

        // This wouldn't compile in MinGW - strange
        /// wxWindow* page = wxDynamicCast( manager->GetChild( wxobject, 0 ), wxWindow );

        // Do this instead
        wxObject* child = GetManager()->GetChild(wxobject, 0);
        wxWindow* page = NULL;
        if (child->IsKindOf(CLASSINFO(wxWindow))) {
            page = (wxWindow*)child;
        }

        // Error checking
        if (!(obj && book && page)) {
            wxLogError(
              _("AuiNotebookPageComponent is missing its wxFormBuilder object(%i), its parent(%i), or its child(%i)"),
              obj, book, page);
            return;
        }

        // Prevent event handling by wxFB - these aren't user generated events
        SuppressEventHandlers suppress(book);

        // Save selection
        int selection = book->GetSelection();
        const wxBitmap& bitmap = obj->IsPropertyNull(_("bitmap")) ? wxNullBitmap : obj->GetPropertyAsBitmap(_("bitmap"));
        book->AddPage(page, obj->GetPropertyAsString(_("label")), false, bitmap);

        if (obj->GetPropertyAsString(_("select")) == wxT("0") && selection >= 0) {
            book->SetSelection(selection);
        } else {
            book->SetSelection(book->GetPageCount() - 1);
        }
    }

    void OnSelected(wxObject* wxobject) override { BookUtils::OnSelected<wxAuiNotebook>(wxobject, GetManager()); }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj, "notebookpage", "");
        filter.AddProperty(XrcFilter::Type::Text, "label");
        filter.AddProperty(XrcFilter::Type::Bool, "select", "selected");
        if (!obj->IsPropertyNull("bitmap")) {
            filter.AddProperty(XrcFilter::Type::Bitmap, "bitmap");
        }
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc, "notebookpage", "");
        filter.AddProperty(XrcFilter::Type::Text, "label");
        filter.AddProperty(XrcFilter::Type::Bool, "selected", "select");
        filter.AddProperty(XrcFilter::Type::Bitmap, "bitmap");
        return xfb;
    }
};

class SimplebookComponent : public ComponentBase
{
public:
    wxObject* Create(IObject* obj, wxObject* parent) override
    {
        return new wxSimplebook(
          (wxWindow*)parent, wxID_ANY, obj->GetPropertyAsPoint(_("pos")), obj->GetPropertyAsSize(_("size")),
          obj->GetPropertyAsInteger(_("window_style")));
    }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj);
        filter.AddWindowProperties();
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc);
        filter.AddWindowProperties();
        return xfb;
    }
};

class SimplebookPageComponent : public ComponentBase
{
public:
    void OnCreated(wxObject* wxobject, wxWindow* wxparent) override
    {
        BookUtils::OnCreated<wxSimplebook>(wxobject, wxparent, GetManager(), _("SimplebookPageComponent"));
    }

    void OnSelected(wxObject* wxobject) override { BookUtils::OnSelected<wxSimplebook>(wxobject, GetManager()); }

    tinyxml2::XMLElement* ExportToXrc(tinyxml2::XMLElement* xrc, const IObject* obj) override
    {
        ObjectToXrcFilter filter(xrc, GetLibrary(), obj, std::nullopt, "");
        filter.AddProperty(XrcFilter::Type::Text, "label");
        filter.AddProperty(XrcFilter::Type::Bool, "select", "selected");
        return xrc;
    }

    tinyxml2::XMLElement* ImportFromXrc(tinyxml2::XMLElement* xfb, const tinyxml2::XMLElement* xrc) override
    {
        XrcToXfbFilter filter(xfb, GetLibrary(), xrc, std::nullopt, "");
        filter.AddProperty(XrcFilter::Type::Text, "label");
        filter.AddProperty(XrcFilter::Type::Bool, "selected", "select");
        return xfb;
    }
};

///////////////////////////////////////////////////////////////////////////////

BEGIN_LIBRARY()

WINDOW_COMPONENT("wxPanel", PanelComponent)

#ifdef wxUSE_COLLPANE
WINDOW_COMPONENT("wxCollapsiblePane", CollapsiblePaneComponent)
#endif

WINDOW_COMPONENT("wxSplitterWindow", SplitterWindowComponent)
ABSTRACT_COMPONENT("splitteritem", SplitterItemComponent)

WINDOW_COMPONENT("wxScrolledWindow", ScrolledWindowComponent)

WINDOW_COMPONENT("wxNotebook", NotebookComponent)
ABSTRACT_COMPONENT("notebookpage", NotebookPageComponent)

WINDOW_COMPONENT("wxListbook", ListbookComponent)
ABSTRACT_COMPONENT("listbookpage", ListbookPageComponent)

WINDOW_COMPONENT("wxChoicebook", ChoicebookComponent)
ABSTRACT_COMPONENT("choicebookpage", ChoicebookPageComponent)

WINDOW_COMPONENT("wxAuiNotebook", AuiNotebookComponent)
ABSTRACT_COMPONENT("auinotebookpage", AuiNotebookPageComponent)

WINDOW_COMPONENT("wxSimplebook", SimplebookComponent)
ABSTRACT_COMPONENT("simplebookpage", SimplebookPageComponent)

#ifdef wxUSE_COLLPANE
// wxCollapsiblePane
MACRO(wxCP_DEFAULT_STYLE)
MACRO(wxCP_NO_TLW_RESIZE)
#endif

// wxSplitterWindow
MACRO(wxSP_3D)
MACRO(wxSP_3DSASH)
MACRO(wxSP_3DBORDER)
MACRO(wxSP_BORDER)
MACRO(wxSP_NOBORDER)
MACRO(wxSP_NOSASH)
MACRO(wxSP_THIN_SASH)
MACRO(wxSP_NO_XP_THEME)
MACRO(wxSP_PERMIT_UNSPLIT)
MACRO(wxSP_LIVE_UPDATE)

MACRO(wxSPLIT_VERTICAL)
MACRO(wxSPLIT_HORIZONTAL)

// wxScrolledWindow
MACRO(wxHSCROLL);
MACRO(wxVSCROLL);

// wxNotebook
MACRO(wxNB_TOP)
MACRO(wxNB_LEFT)
MACRO(wxNB_RIGHT)
MACRO(wxNB_BOTTOM)
MACRO(wxNB_FIXEDWIDTH)
MACRO(wxNB_MULTILINE)
MACRO(wxNB_NOPAGETHEME)

// wxListbook
MACRO(wxLB_TOP)
MACRO(wxLB_LEFT)
MACRO(wxLB_RIGHT)
MACRO(wxLB_BOTTOM)
MACRO(wxLB_DEFAULT)

// wxChoicebook
MACRO(wxCHB_TOP)
MACRO(wxCHB_LEFT)
MACRO(wxCHB_RIGHT)
MACRO(wxCHB_BOTTOM)
MACRO(wxCHB_DEFAULT)

// wxAuiNotebook
MACRO(wxAUI_NB_DEFAULT_STYLE)
MACRO(wxAUI_NB_TAB_SPLIT)
MACRO(wxAUI_NB_TAB_MOVE)
MACRO(wxAUI_NB_TAB_EXTERNAL_MOVE)
MACRO(wxAUI_NB_TAB_FIXED_WIDTH)
MACRO(wxAUI_NB_SCROLL_BUTTONS)
MACRO(wxAUI_NB_WINDOWLIST_BUTTON)
MACRO(wxAUI_NB_CLOSE_BUTTON)
MACRO(wxAUI_NB_CLOSE_ON_ACTIVE_TAB)
MACRO(wxAUI_NB_CLOSE_ON_ALL_TABS)
MACRO(wxAUI_NB_MIDDLE_CLICK_CLOSE)
MACRO(wxAUI_NB_TOP)
MACRO(wxAUI_NB_BOTTOM)

END_LIBRARY()
