From fe7df68383ee6f2dbbfd03df97af4076008a42f9 Mon Sep 17 00:00:00 2001 From: Vadim Zeitlin Date: Mon, 9 Dec 2024 17:01:58 +0100 Subject: [PATCH] Add support for saving and restoring wxAuiNotebook layout This includes multiple tab controls and pages order and uses new wxAuiSerializer and wxAuiDeserializer notebook-specific functions. These functions are pure virtual, which may be inconvenient if the program doesn't use wxAuiNotebook at all or doesn't allow splitting or reordering their pages, as it forces to still define them in this case, but this is arguably not too burdensome and making them pure virtual helps to attract attention to the fact that they need to be implemented for saving notebook layouts. Update the sample to show how to support notebook layout serialization in the simplest possible way, by creating a separate top-level XML tag for the notebooks. It could be tidier to put information about them inside the corresponding pane tags, but doing it like this is simpler and more clear, which is important for the sample code. Closes #24950. --- include/wx/aui/auibook.h | 8 ++ include/wx/aui/serializer.h | 35 ++++++ interface/wx/aui/serializer.h | 125 +++++++++++++++++--- samples/aui/auidemo.cpp | 211 ++++++++++++++++++++++++++++------ src/aui/auibook.cpp | 195 +++++++++++++++++++++++++++++++ src/aui/framemanager.cpp | 41 ++++++- 6 files changed, 561 insertions(+), 54 deletions(-) diff --git a/include/wx/aui/auibook.h b/include/wx/aui/auibook.h index 62c9af46b4..8051ccd7c3 100644 --- a/include/wx/aui/auibook.h +++ b/include/wx/aui/auibook.h @@ -26,6 +26,9 @@ #include "wx/compositebookctrl.h" +class wxAuiSerializer; +class wxAuiDeserializer; + class wxAuiNotebook; class wxAuiTabFrame; @@ -432,6 +435,11 @@ public: // Internal, don't use: use GetPagePosition() instead. bool FindTab(wxWindow* page, wxAuiTabCtrl** ctrl, int* idx) const; + // Serialization support: this is only used by wxAuiManager, don't use + // directly. + void SaveLayout(const wxString& name, wxAuiSerializer& serializer) const; + void LoadLayout(const wxString& name, wxAuiDeserializer& deserializer); + protected: // Common part of all ctors. void Init(); diff --git a/include/wx/aui/serializer.h b/include/wx/aui/serializer.h index bc510e1f11..94f40606dd 100644 --- a/include/wx/aui/serializer.h +++ b/include/wx/aui/serializer.h @@ -16,6 +16,8 @@ // Classes used to save/load wxAuiManager layout. // ---------------------------------------------------------------------------- +// Fields common to wxAuiPaneLayoutInfo and wxAuiTabLayoutInfo containing +// information about a docked pane or tab layout. struct wxAuiDockLayoutInfo { // Identifies the dock containing the pane. @@ -33,6 +35,15 @@ struct wxAuiDockLayoutInfo int dock_size = 0; }; +// This struct contains information about the layout of a tab control in a +// wxAuiNotebook, including where it is docked and the order of pages in it. +struct wxAuiTabLayoutInfo : wxAuiDockLayoutInfo +{ + // If this vector is empty, it means that the tab control contains all + // notebook pages in natural order. + std::vector pages; +}; + // This struct contains the pane name and information about its layout that can // be manipulated by the user interactively. struct wxAuiPaneLayoutInfo : wxAuiDockLayoutInfo @@ -87,6 +98,25 @@ public: // Called after the last call to SavePane(), does nothing by default. virtual void AfterSavePanes() { } + // Called before starting to save information about the notebooks, does + // nothing by default. + virtual void BeforeSaveNotebooks() { } + + // Called before starting to save information about the tabs in the + // notebook in the AUI pane with the given name. + virtual void BeforeSaveNotebook(const wxString& name) = 0; + + // Called to save information about a single tab control in the given + // notebook. + virtual void SaveNotebookTabControl(const wxAuiTabLayoutInfo& tab) = 0; + + // Called after saving information about all the pages of the notebook in + // the AUI pane with the given name, does nothing by default. + virtual void AfterSaveNotebook() { } + + // Called after the last call to SaveNotebook(), does nothing by default. + virtual void AfterSaveNotebooks() { } + // Called after saving everything, does nothing by default. virtual void AfterSave() { } }; @@ -116,6 +146,11 @@ public: // Load information about all the panes previously saved with SavePane(). virtual std::vector LoadPanes() = 0; + // For a pane containing wxAuiNotebook, load information about all the tab + // controls inside it. + virtual std::vector + LoadNotebookTabs(const wxString& name) = 0; + // Create the window to be managed by the given pane: this is called if any // of the panes returned by LoadPanes() doesn't exist in the existing // layout and allows to create windows on the fly. diff --git a/interface/wx/aui/serializer.h b/interface/wx/aui/serializer.h index ac9880c99d..d0387f6ce4 100644 --- a/interface/wx/aui/serializer.h +++ b/interface/wx/aui/serializer.h @@ -8,25 +8,16 @@ ///////////////////////////////////////////////////////////////////////////// /** - Description of user-modifiable pane layout information. + Description of a docked element layout. - This struct is used with wxAuiSerializer and wxAuiDeserializer to store the - pane layout. Its fields have the same meaning as the corresponding fields - in wxAuiPaneInfo (with the exception of `is_maximized`), but it doesn't - contain the fields that it wouldn't make sense to serialize. + The fields in this struct are shared by wxAuiPaneLayoutInfo and + wxAuiTabLayoutInfo and contain information about the layout of a docked + pane or tab layout. @since 3.3.0 - */ -struct wxAuiPaneLayoutInfo +*/ +struct wxAuiDockLayoutInfo { - /** - Ctor sets the name, which is always required. - */ - explicit wxAuiPaneLayoutInfo(wxString name); - - /// Unique name of the pane. - wxString name; - /// Direction of the dock containing the pane. int dock_direction = wxAUI_DOCK_LEFT; @@ -44,7 +35,47 @@ struct wxAuiPaneLayoutInfo /// Size of the containing dock. int dock_size = 0; +}; +/** + Contains information about the layout of a tab control in a wxAuiNotebook. + + This includes where it is docked, via the fields inherited from + wxAuiDockLayoutInfo, and the order of pages in it. + + @since 3.3.0 +*/ +struct wxAuiTabLayoutInfo : wxAuiDockLayoutInfo +{ + /** + Indices of the pages in this tab control in their order on screen. + + If this vector is empty, it means that the tab control contains all + notebook pages in natural order. + */ + std::vector pages; +}; + +/** + Description of user-modifiable pane layout information. + + This struct is used with wxAuiSerializer and wxAuiDeserializer to store the + pane layout. Its fields, including the inherited ones from + wxAuiDockLayoutInfo, have the same meaning as the corresponding fields in + wxAuiPaneInfo (with the exception of `is_maximized`), but it doesn't + contain the fields that it wouldn't make sense to serialize. + + @since 3.3.0 + */ +struct wxAuiPaneLayoutInfo : wxAuiDockLayoutInfo +{ + /** + Ctor sets the name, which is always required. + */ + explicit wxAuiPaneLayoutInfo(wxString name); + + /// Unique name of the pane. + wxString name; /// Position of the pane when floating, may be invalid. wxPoint floating_pos = wxDefaultPosition; @@ -126,6 +157,57 @@ public: */ virtual void AfterSavePanes(); + /** + Called before starting to save information about the notebooks. + + Does nothing by default. + + Note that this function is called after AfterSavePanes() but may not be + called at all if there are no panes containing wxAuiNotebook. + */ + virtual void BeforeSaveNotebooks(); + + /** + Called before starting to save information about the tabs in the + notebook in the AUI pane with the given name. + + This function needs to be overridden to keep record of the notebook for + which SaveNotebookTabControl() will be called next. Of course, if + saving notebook layout is unnecessary, e.g. because the program doesn't + use wxAuiNotebook at all, the implementation can be trivial and just do + nothing. + + This function is called one or more times after BeforeSaveNotebooks(). + */ + virtual void BeforeSaveNotebook(const wxString& name) = 0; + + /** + Called to save information about a single tab control in the given + notebook. + + This function will be called for all tab controls in the notebook after + BeforeSaveNotebook(). + + As with that function, it has to be implemented, but can simply do + nothing if saving notebook layout is not necessary. + */ + virtual void SaveNotebookTabControl(const wxAuiTabLayoutInfo& tab) = 0; + + /** + Called after saving information about all the pages of the notebook in + the AUI pane with the given name. + + Does nothing by default. + */ + virtual void AfterSaveNotebook(); + + /** + Called after the last call to SaveNotebook(). + + Does nothing by default. + */ + virtual void AfterSaveNotebooks(); + /** Called after saving everything. @@ -190,6 +272,19 @@ public: */ virtual std::vector LoadPanes() = 0; + /** + Load information about the notebook tabs previously saved by + wxAuiSerializer::SaveNotebookTabControl(). + + The pane with the name @a name is guaranteed to exist in the layout and + have wxAuiNotebook as the associated window. + + If restoring the notebook layout is not necessary, this function can + just return an empty vector. + */ + virtual std::vector + LoadNotebookTabs(const wxString& name) = 0; + /** Create the window to be managed by the given pane if necessary. diff --git a/samples/aui/auidemo.cpp b/samples/aui/auidemo.cpp index eab1ad5549..1a61b8d373 100644 --- a/samples/aui/auidemo.cpp +++ b/samples/aui/auidemo.cpp @@ -1544,15 +1544,7 @@ public: auto node = new wxXmlNode(wxXML_ELEMENT_NODE, "pane"); node->AddAttribute("name", pane.name); - AddChild(node, "direction", pane.dock_direction); - AddChild(node, "layer", pane.dock_layer); - AddChild(node, "row", pane.dock_row); - AddChild(node, "position", pane.dock_pos); - AddChild(node, "proportion", pane.dock_proportion); - - // Saving dock size of 0 is harmless but unnecessary, so don't do it. - if ( pane.dock_size ) - AddChild(node, "size", pane.dock_size); + AddDockLayout(node, pane); AddChild(node, "floating-rect", wxRect(pane.floating_pos, pane.floating_size)); @@ -1570,6 +1562,54 @@ public: m_root->AddChild(m_panes.release()); } + + virtual void BeforeSaveNotebooks() override + { + m_books.reset(new wxXmlNode(wxXML_ELEMENT_NODE, "notebooks")); + } + + virtual void BeforeSaveNotebook(const wxString& name) override + { + m_book.reset(new wxXmlNode(wxXML_ELEMENT_NODE, "notebook")); + m_book->AddAttribute("name", name); + } + + virtual void SaveNotebookTabControl(const wxAuiTabLayoutInfo& tab) override + { + auto node = new wxXmlNode(wxXML_ELEMENT_NODE, "tab"); + + AddDockLayout(node, tab); + + // We don't need to save the pages order if the vector is empty, this + // means that this tab contains all the notebook pages in default order. + if ( !tab.pages.empty() ) + { + wxString pagesList; + for ( auto page : tab.pages ) + { + if ( !pagesList.empty() ) + pagesList << ','; + + pagesList << page; + } + + AddChild(node, "pages", pagesList); + } + + m_book->AddChild(node); + } + + virtual void AfterSaveNotebook() override + { + m_books->AddChild(m_book.release()); + } + + virtual void AfterSaveNotebooks() override + { + m_root->AddChild(m_books.release()); + } + + virtual void AfterSave() override {} private: @@ -1595,6 +1635,22 @@ private: } } + // Common helper of SavePane() and SaveNotebookTabControl() which both need + // to save the same layout information. + void AddDockLayout(wxXmlNode* node, const wxAuiDockLayoutInfo& layout) + { + AddChild(node, "direction", layout.dock_direction); + AddChild(node, "layer", layout.dock_layer); + AddChild(node, "row", layout.dock_row); + AddChild(node, "position", layout.dock_pos); + AddChild(node, "proportion", layout.dock_proportion); + + // Saving dock size of 0 is harmless but unnecessary, so don't do it. + if ( layout.dock_size ) + AddChild(node, "size", layout.dock_size); + } + + wxXmlDocument m_doc; // Non-owning pointer to the root node of m_doc. @@ -1604,6 +1660,8 @@ private: // document -- this ensures that we don't leak memory if an exception is // thrown before this happens. std::unique_ptr m_panes; + std::unique_ptr m_books; + std::unique_ptr m_book; }; class MyXmlDeserializer : public wxAuiDeserializer @@ -1627,6 +1685,13 @@ public: if ( layout->GetNext() ) throw std::runtime_error("Unexpected multiple layout nodes"); + // Check that we only have the top level nodes that we expect. + // + // This is nice to detect errors in this sample, but note that this + // might not be the best strategy for a real application, which might + // decide to to gracefully ignore unknown nodes instead of failing, or + // at least save the format version in the XML file to be able to give + // a better error message. for ( wxXmlNode* node = layout->GetChildren(); node; node = node->GetNext() ) { if ( node->GetName() == "panes" ) @@ -1636,6 +1701,13 @@ public: m_panes = node; } + else if ( node->GetName() == "notebooks" ) + { + if ( m_books ) + throw std::runtime_error("Unexpected multiple notebooks nodes"); + + m_books = node; + } else { throw std::runtime_error("Unexpected node name"); @@ -1661,34 +1733,13 @@ public: for ( wxXmlNode* child = node->GetChildren(); child; child = child->GetNext() ) { + if ( LoadDockLayout(child, pane) ) + continue; + const wxString& name = child->GetName(); const wxString& content = child->GetNodeContent(); - if ( name == "direction" ) - { - pane.dock_direction = GetInt(content); - } - else if ( name == "layer" ) - { - pane.dock_layer = GetInt(content); - } - else if ( name == "row" ) - { - pane.dock_row = GetInt(content); - } - else if ( name == "position" ) - { - pane.dock_pos = GetInt(content); - } - else if ( name == "proportion" ) - { - pane.dock_proportion = GetInt(content); - } - else if ( name == "size" ) - { - pane.dock_size = GetInt(content); - } - else if ( name == "floating-rect" ) + if ( name == "floating-rect" ) { auto rect = GetRect(content); @@ -1712,6 +1763,26 @@ public: return panes; } + virtual std::vector + LoadNotebookTabs(const wxString& name) override + { + // Find the notebook with the given name. + for ( wxXmlNode* node = m_books->GetChildren(); node; node = node->GetNext() ) + { + if ( node->GetName() != "notebook" ) + throw std::runtime_error("Unexpected notebook node name"); + + if ( node->GetAttribute("name") == name ) + return LoadNotebookTabs(node); + } + + // As above, this might not be the best thing to do in a real + // application, where, perhaps, the XML file was saved by a newer + // version of the problem, but here we do this for simplicity and to + // make sure we detect any errors. + throw std::runtime_error("Notebook with the given name not found"); + } + virtual wxWindow* CreatePaneWindow(wxAuiPaneInfo& pane) override { wxLogWarning("Unknown pane \"%s\"", pane.name); @@ -1755,11 +1826,83 @@ private: return wxRect(wxPoint(x, y), GetSize(strWH)); } + // Common helper of LoadPanes() and LoadNotebookTabs() which both need to + // load the dock layout information. + // + // Returns true if we processed this node. + bool LoadDockLayout(wxXmlNode* node, wxAuiDockLayoutInfo& info) + { + const wxString& name = node->GetName(); + const wxString& content = node->GetNodeContent(); + + if ( name == "direction" ) + { + info.dock_direction = GetInt(content); + } + else if ( name == "layer" ) + { + info.dock_layer = GetInt(content); + } + else if ( name == "row" ) + { + info.dock_row = GetInt(content); + } + else if ( name == "position" ) + { + info.dock_pos = GetInt(content); + } + else if ( name == "proportion" ) + { + info.dock_proportion = GetInt(content); + } + else if ( name == "size" ) + { + info.dock_size = GetInt(content); + } + else + { + return false; + } + + return true; + } + + std::vector LoadNotebookTabs(wxXmlNode* book) + { + std::vector tabs; + + for ( wxXmlNode* node = book->GetChildren(); node; node = node->GetNext() ) + { + if ( node->GetName() != "tab" ) + throw std::runtime_error("Unexpected tab node name"); + + wxAuiTabLayoutInfo tab; + for ( wxXmlNode* child = node->GetChildren(); child; child = child->GetNext() ) + { + if ( LoadDockLayout(child, tab) ) + continue; + + if ( child->GetName() != "pages" ) + throw std::runtime_error("Unexpected tab child node name"); + + for ( const auto& s : wxSplit(child->GetNodeContent(), ',') ) + { + tab.pages.push_back(GetInt(s)); + } + } + + tabs.push_back(tab); + } + + return tabs; + } + wxXmlDocument m_doc; // Non-owning pointers to the nodes in m_doc. wxXmlNode* m_panes = nullptr; + wxXmlNode* m_books = nullptr; }; void MyFrame::OnCopyLayout(wxCommandEvent& WXUNUSED(evt)) diff --git a/src/aui/auibook.cpp b/src/aui/auibook.cpp index 1c960010cb..dd2ea2950a 100644 --- a/src/aui/auibook.cpp +++ b/src/aui/auibook.cpp @@ -26,6 +26,7 @@ #include "wx/frame.h" #endif +#include "wx/aui/serializer.h" #include "wx/aui/tabmdi.h" #include "wx/wupdlock.h" @@ -35,6 +36,8 @@ #include "wx/osx/private.h" #endif +#include + wxDEFINE_EVENT(wxEVT_AUINOTEBOOK_PAGE_CLOSE, wxAuiNotebookEvent); wxDEFINE_EVENT(wxEVT_AUINOTEBOOK_PAGE_CLOSED, wxAuiNotebookEvent); wxDEFINE_EVENT(wxEVT_AUINOTEBOOK_PAGE_CHANGING, wxAuiNotebookEvent); @@ -3686,5 +3689,197 @@ void wxAuiTabCtrl::SetHoverTab(wxWindow* wnd) } } +// ---------------------------------------------------------------------------- +// Layout serialization +// ---------------------------------------------------------------------------- + +void +wxAuiNotebook::SaveLayout(const wxString& name, + wxAuiSerializer& serializer) const +{ + serializer.BeforeSaveNotebook(name); + + for ( const auto& pane : m_mgr.GetAllPanes() ) + { + if ( IsDummyPane(pane) ) + continue; + + wxAuiTabLayoutInfo tab; + m_mgr.CopyDockLayoutFrom(tab, pane); + + const wxAuiTabCtrl* const + tabCtrl = static_cast(pane.window)->m_tabs; + + // As an optimization, don't bother with saving the pages order for the + // main control if it hasn't been changed from the default. + bool mustSavePages = false; + if ( tab.dock_direction != wxAUI_DOCK_CENTER ) + { + // The non-main tab controls can't possibly have all the pages, so + // we always have to save the indices of the ones they contain. + mustSavePages = true; + } + else if ( tabCtrl->GetPageCount() != GetPageCount() ) + { + // If the main control doesn't have all the pages, we need to save + // them for it as well. + mustSavePages = true; + } + + std::vector pages; + int n = 0; + for ( const auto& page : tabCtrl->GetPages() ) + { + // This is inefficient for many pages, we should consider using a + // hash map indexed by the window pointer if this proves to be a + // problem in practice. + const int idx = m_tabs.GetIdxFromWindow(page.window); + if ( idx != n++ ) + { + // And if the pages are not in order, we must save them too. + mustSavePages = true; + } + + pages.push_back(idx); + } + + // But if none of the conditions above is true, we can avoid saving + // them, which also allows us not to remove and re-add them when + // restoring later, see LoadLayout() below. + if ( mustSavePages ) + tab.pages = std::move(pages); + + serializer.SaveNotebookTabControl(tab); + } + + serializer.AfterSaveNotebook(); +} + +void +wxAuiNotebook::LoadLayout(const wxString& name, wxAuiDeserializer& deserializer) +{ + const auto tabs = deserializer.LoadNotebookTabs(name); + + if ( tabs.empty() ) + return; + + // Remove any existing tabs before adding new ones. + UnsplitAll(); + + // Get the only remaining tab control. + wxAuiTabCtrl* const tabMain = GetMainTabCtrl(); + + // Keep track of pages we've already added to some tab control: even if the + // deserialized data is somehow incorrect and duplicates the page indices, + // we don't want to try to have the same page in more than one tab control. + std::unordered_set addedPages; + const int pageCount = m_tabs.GetPageCount(); + + for ( const auto& tab : tabs ) + { + wxAuiTabCtrl* tabCtrl = nullptr; + + // The pages pointer will be set to point to either tab.pages or + // pageDefault in which case it will also be filled. + std::vector pagesDefault; + const std::vector* pages = nullptr; + + // We don't need to create a new pane for the main, central tab control, + // but we still need to order its tabs correctly. + if ( tab.dock_direction == wxAUI_DOCK_CENTER ) + { + // Special case: if the pages vector is empty check if we're using + // the default order, in which case we can avoid doing anything if + // the pages are already in the same order (which is a common case). + if ( tab.pages.empty() ) + { + bool mustRestore = false; + for ( int i = 0; i < pageCount; ++i ) + { + if ( tabMain->GetWindowFromIdx(i) != m_tabs.GetWindowFromIdx(i) ) + { + mustRestore = true; + break; + } + } + + if ( !mustRestore ) + { + // All pages are in the main tab in the default order + // already, so we don't have anything to do. + break; + } + + // The pages had been reordered, restore the default order when + // re-adding them below. + for ( int i = 0; i < pageCount; ++i ) + pagesDefault.push_back(i); + + pages = &pagesDefault; + } + else + { + // Just use the specified pages. + pages = &tab.pages; + } + + // In any case, we must remove all pages currently in this tab as + // they will be re-added. + tabMain->RemoveAll(); + + tabCtrl = tabMain; + } + else // Non-central tab control. + { + // In this case, we must have some pages, but if we somehow don't, + // just skip adding the tab control entirely. + if ( tab.pages.empty() ) + continue; + + // Re-add using the saved layout. + wxAuiPaneInfo pane; + m_mgr.CopyDockLayoutTo(tab, pane); + + // Our panes never show caption. + pane.CaptionVisible(false); + + wxAuiTabFrame* tabframe = CreateTabFrame(); + m_mgr.AddPane(tabframe, pane); + + tabCtrl = tabframe->m_tabs; + pages = &tab.pages; + } + + // In any case, add the pages that this tab control had before to it. + bool first = true; + for ( auto page : *pages ) + { + // We just silently ignore invalid or duplicate page indices + // here, because it doesn't seem right for them to result in a + // fatal error and any warnings would be just annoying. + if ( page >= pageCount ) + continue; + + if ( !addedPages.insert(page).second ) + continue; + + auto info = m_tabs.GetPage(page); + + // We don't save the last active page currently, so just make the + // first one active. + if ( first ) + { + info.active = true; + first = false; + } + + tabCtrl->AddPage(info); + } + + tabCtrl->DoUpdateActive(); + } + + m_mgr.Update(); +} #endif // wxUSE_AUI diff --git a/src/aui/framemanager.cpp b/src/aui/framemanager.cpp index 0fc7065a35..5d4645d445 100644 --- a/src/aui/framemanager.cpp +++ b/src/aui/framemanager.cpp @@ -25,6 +25,7 @@ #include "wx/aui/floatpane.h" #include "wx/aui/tabmdi.h" #include "wx/aui/auibar.h" +#include "wx/aui/auibook.h" #include "wx/aui/serializer.h" #include "wx/mdi.h" #include "wx/wupdlock.h" @@ -69,6 +70,7 @@ wxDEFINE_EVENT( wxEVT_AUI_FIND_MANAGER, wxAuiManagerEvent ); #include "wx/generic/private/drawresize.h" +#include #include wxIMPLEMENT_DYNAMIC_CLASS(wxAuiManagerEvent, wxEvent); @@ -1451,6 +1453,10 @@ void wxAuiManager::SaveLayout(wxAuiSerializer& serializer) const { serializer.BeforeSavePanes(); + // Collect information about all the notebooks we may have while saving + // the panes layout. + std::map notebooks; + for ( const auto& pane : m_panes ) { wxAuiPaneLayoutInfo layoutInfo{pane.name}; @@ -1460,9 +1466,26 @@ void wxAuiManager::SaveLayout(wxAuiSerializer& serializer) const MakeDIP(m_frame, layoutInfo.floating_size); serializer.SavePane(layoutInfo); + + if ( auto* const nb = wxDynamicCast(pane.window, wxAuiNotebook) ) + { + notebooks[pane.name] = nb; + } } serializer.AfterSavePanes(); + + if ( !notebooks.empty() ) + { + serializer.BeforeSaveNotebooks(); + + for ( const auto& kv : notebooks ) + { + kv.second->SaveLayout(kv.first, serializer); + } + + serializer.AfterSaveNotebooks(); + } } serializer.AfterSave(); @@ -1504,7 +1527,7 @@ void wxAuiManager::LoadLayout(wxAuiDeserializer& deserializer) hasMaximized = true; // Find the pane with the same name in the existing layout. - bool found = false; + wxWindow* window = nullptr; for ( auto& existingPane : panes ) { if ( existingPane.name == layoutInfo.name ) @@ -1512,21 +1535,29 @@ void wxAuiManager::LoadLayout(wxAuiDeserializer& deserializer) // Update the existing pane with the restored layout. CopyLayoutTo(layoutInfo, existingPane); - found = true; + window = existingPane.window; break; } } // This pane couldn't be found in the existing layout, let deserializer // create a new window for it if desired, otherwise just ignore it. - if ( !found ) + if ( !window ) { wxAuiPaneInfo pane; pane.name = layoutInfo.name; CopyLayoutTo(layoutInfo, pane); - if ( const auto w = deserializer.CreatePaneWindow(pane) ) - newPanes.emplace_back(w, pane); + window = deserializer.CreatePaneWindow(pane); + if ( !window ) + continue; + + newPanes.emplace_back(window, pane); + } + + if ( auto* const nb = wxDynamicCast(window, wxAuiNotebook) ) + { + nb->LoadLayout(layoutInfo.name, deserializer); } }