From 61ab7cc91506f84dc9dcdd48ca393ddd4f6e06cc Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Mon, 22 Sep 2025 12:38:46 -0700 Subject: [PATCH] ADDED: AUI Memory Using the new wx3.3 routines to serialize the state of our AUI elements, we store them using JSON in the preferences for each window I have left the SavePerspective in place for now to see how legacy (mostly *nix) builds handle this and provide a smooth transition to 3.3 Fixes https://gitlab.com/kicad/code/kicad/-/issues/2381 --- common/CMakeLists.txt | 1 + common/eda_base_frame.cpp | 41 ++ common/settings/app_settings.cpp | 5 + common/widgets/aui_json_serializer.cpp | 623 +++++++++++++++++++ cvpcb/display_footprints_frame.cpp | 1 + eeschema/sch_edit_frame.cpp | 1 + eeschema/symbol_editor/symbol_edit_frame.cpp | 7 +- eeschema/symbol_viewer_frame.cpp | 1 + gerbview/CMakeLists.txt | 1 + include/eda_base_frame.h | 5 + include/settings/app_settings.h | 2 + include/widgets/aui_json_serializer.h | 40 ++ pcbnew/footprint_edit_frame.cpp | 2 + pcbnew/footprint_wizard_frame.cpp | 19 + pcbnew/footprint_wizard_frame.h | 2 + pcbnew/pcb_edit_frame.cpp | 2 +- 16 files changed, 749 insertions(+), 4 deletions(-) create mode 100644 common/widgets/aui_json_serializer.cpp create mode 100644 include/widgets/aui_json_serializer.h diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index f98bf83483..fb0ec52e9a 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -166,6 +166,7 @@ set( KICOMMON_SRCS dialogs/dialog_rc_job_base.cpp widgets/bitmap_button.cpp + widgets/aui_json_serializer.cpp widgets/html_window.cpp widgets/kistatusbar.cpp widgets/number_badge.cpp diff --git a/common/eda_base_frame.cpp b/common/eda_base_frame.cpp index 3581790ee5..9e6aa52fe1 100644 --- a/common/eda_base_frame.cpp +++ b/common/eda_base_frame.cpp @@ -64,6 +64,7 @@ #include #include #include +#include #include #include #include @@ -78,6 +79,8 @@ #include #include +#include + #include #include @@ -938,6 +941,7 @@ void EDA_BASE_FRAME::LoadWindowSettings( const WINDOW_SETTINGS* aCfg ) LoadWindowState( aCfg->state ); m_perspective = aCfg->perspective; + m_auiLayoutState = aCfg->aui_state; m_mruPath = aCfg->mru_path; TOOLS_HOLDER::CommonSettingsChanged(); @@ -976,7 +980,22 @@ void EDA_BASE_FRAME::SaveWindowSettings( WINDOW_SETTINGS* aCfg ) // Once this is fully implemented, wxAuiManager will be used to maintain // the persistence of the main frame and all it's managed windows and // all of the legacy frame persistence position code can be removed. +#if wxCHECK_VERSION( 3, 3, 0 ) + { + WX_AUI_JSON_SERIALIZER serializer( m_auimgr ); + nlohmann::json state = serializer.Serialize(); + + if( state.is_null() || state.empty() ) + aCfg->aui_state = nlohmann::json(); + else + aCfg->aui_state = state; + + aCfg->perspective.clear(); + } +#else aCfg->perspective = m_auimgr.SavePerspective().ToStdString(); + aCfg->aui_state = nlohmann::json(); +#endif aCfg->mru_path = m_mruPath; } @@ -1084,6 +1103,28 @@ void EDA_BASE_FRAME::FinishAUIInitialization() } +void EDA_BASE_FRAME::RestoreAuiLayout() +{ +#if wxCHECK_VERSION( 3, 3, 0 ) + bool restored = false; + + if( !m_auiLayoutState.is_null() && !m_auiLayoutState.empty() ) + { + WX_AUI_JSON_SERIALIZER serializer( m_auimgr ); + + if( serializer.Deserialize( m_auiLayoutState ) ) + restored = true; + } + + if( !restored && !m_perspective.IsEmpty() ) + m_auimgr.LoadPerspective( m_perspective ); +#else + if( !m_perspective.IsEmpty() ) + m_auimgr.LoadPerspective( m_perspective ); +#endif +} + + void EDA_BASE_FRAME::ShowInfoBarError( const wxString& aErrorMsg, bool aShowCloseButton, WX_INFOBAR::MESSAGE_TYPE aType ) { diff --git a/common/settings/app_settings.cpp b/common/settings/app_settings.cpp index 37687f76f8..8ed4709607 100644 --- a/common/settings/app_settings.cpp +++ b/common/settings/app_settings.cpp @@ -30,6 +30,8 @@ #include #include #include + +#include #include @@ -425,6 +427,9 @@ void APP_SETTINGS_BASE::addParamsForWindow( WINDOW_SETTINGS* aWindow, const std: m_params.emplace_back( new PARAM( aJsonPath + ".perspective", &aWindow->perspective, wxS( "" ) ) ); + m_params.emplace_back( new PARAM( aJsonPath + ".aui_state", + &aWindow->aui_state, nlohmann::json() ) ); + m_params.emplace_back( new PARAM( aJsonPath + ".pos_x", &aWindow->state.pos_x, 0 ) ); m_params.emplace_back( new PARAM( aJsonPath + ".pos_y", &aWindow->state.pos_y, 0 ) ); diff --git a/common/widgets/aui_json_serializer.cpp b/common/widgets/aui_json_serializer.cpp new file mode 100644 index 0000000000..6835dc76ae --- /dev/null +++ b/common/widgets/aui_json_serializer.cpp @@ -0,0 +1,623 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * 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 3 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, see . + */ + +#include + +#include + +#include +#if wxCHECK_VERSION( 3, 3, 0 ) +#include +#include +#endif +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace +{ +#if wxCHECK_VERSION( 3, 3, 0 ) + +struct PANE_METADATA +{ + wxAuiPaneInfo* paneInfo; + wxString name; + wxString windowName; + wxString className; + wxString caption; + bool isToolbar; + bool isCenter; + bool isNotebook; + size_t index; +}; + +static wxString getWindowName( wxWindow* aWindow ) +{ + if( !aWindow ) + return wxString(); + + wxString name = aWindow->GetName(); + + if( name.IsEmpty() ) + // Fallback: use label if available (GetWindowVariant returns enum, not a string) + name = aWindow->GetLabel(); + + return name; +} + +static PANE_METADATA buildMetadata( wxAuiPaneInfo& aInfo, size_t aIndex ) +{ + PANE_METADATA meta; + + meta.paneInfo = &aInfo; + meta.name = aInfo.name; + meta.caption = aInfo.caption; + meta.isToolbar = aInfo.IsToolbar(); + meta.isCenter = ( aInfo.dock_direction == wxAUI_DOCK_CENTER ); + meta.isNotebook = aInfo.window && wxDynamicCast( aInfo.window, wxAuiNotebook ); + meta.index = aIndex; + + if( aInfo.window ) + { + meta.windowName = getWindowName( aInfo.window ); + + if( aInfo.window->GetClassInfo() ) + meta.className = aInfo.window->GetClassInfo()->GetClassName(); + } + + return meta; +} + +static void addDockLayout( nlohmann::json& aNode, const wxAuiDockLayoutInfo& aLayout ) +{ + nlohmann::json dock = nlohmann::json::object(); + + dock["direction"] = aLayout.dock_direction; + dock["layer"] = aLayout.dock_layer; + dock["row"] = aLayout.dock_row; + dock["position"] = aLayout.dock_pos; + dock["proportion"] = aLayout.dock_proportion; + dock["size"] = aLayout.dock_size; + + aNode["dock"] = std::move( dock ); +} + +static void readDockLayout( const nlohmann::json& aNode, wxAuiDockLayoutInfo& aLayout ) +{ + if( !aNode.is_object() ) + return; + + aLayout.dock_direction = aNode.value( "direction", aLayout.dock_direction ); + aLayout.dock_layer = aNode.value( "layer", aLayout.dock_layer ); + aLayout.dock_row = aNode.value( "row", aLayout.dock_row ); + aLayout.dock_pos = aNode.value( "position", aLayout.dock_pos ); + aLayout.dock_proportion = aNode.value( "proportion", aLayout.dock_proportion ); + aLayout.dock_size = aNode.value( "size", aLayout.dock_size ); +} + +class JSON_SERIALIZER : public wxAuiSerializer +{ +public: + explicit JSON_SERIALIZER( wxAuiManager& aManager ) : m_manager( aManager ), m_paneIndex( 0 ) + { + m_root = nlohmann::json::object(); + } + + nlohmann::json GetState() const + { + return m_root; + } + + void BeforeSave() override + { + m_root["format"] = "kicad.wxaui"; + m_root["version"] = 1; + } + + void BeforeSavePanes() override + { + m_panes = nlohmann::json::array(); + m_paneIndex = 0; + } + + void SavePane( const wxAuiPaneLayoutInfo& aPane ) override + { + nlohmann::json pane = nlohmann::json::object(); + + pane["name"] = aPane.name.ToStdString(); + + addDockLayout( pane, aPane ); + + if( aPane.floating_pos != wxDefaultPosition || aPane.floating_size != wxDefaultSize ) + { + nlohmann::json floating = nlohmann::json::object(); + floating["rect"] = wxRect( aPane.floating_pos, aPane.floating_size ); + + pane["floating"] = std::move( floating ); + } + + if( aPane.is_maximized ) + pane["maximized"] = true; + + if( aPane.is_hidden ) + pane["hidden"] = true; + + wxAuiPaneInfo& info = m_manager.GetPane( aPane.name ); + + nlohmann::json meta = nlohmann::json::object(); + meta["toolbar"] = info.IsToolbar(); + meta["center"] = ( info.dock_direction == wxAUI_DOCK_CENTER ); + meta["notebook"] = info.window && wxDynamicCast( info.window, wxAuiNotebook ); + meta["index"] = m_paneIndex++; + + if( info.window ) + { + wxString windowName = getWindowName( info.window ); + + if( !windowName.IsEmpty() ) + meta["window_name"] = windowName.ToStdString(); + + if( info.window->GetClassInfo() ) + meta["class_name"] = wxString( info.window->GetClassInfo()->GetClassName() ).ToStdString(); + } + + if( !info.caption.IsEmpty() ) + meta["caption"] = info.caption.ToStdString(); + + pane["meta"] = std::move( meta ); + + m_panes.push_back( std::move( pane ) ); + } + + void AfterSavePanes() override + { + m_root["panes"] = std::move( m_panes ); + } + + void BeforeSaveNotebooks() override + { + m_notebooks = nlohmann::json::array(); + } + + void BeforeSaveNotebook( const wxString& aName ) override + { + m_currentNotebook = nlohmann::json::object(); + m_currentNotebook["name"] = aName.ToStdString(); + + wxAuiPaneInfo& info = m_manager.GetPane( aName ); + nlohmann::json meta = nlohmann::json::object(); + + if( info.window ) + { + wxString windowName = getWindowName( info.window ); + + if( !windowName.IsEmpty() ) + meta["window_name"] = windowName.ToStdString(); + + if( info.window->GetClassInfo() ) + meta["class_name"] = wxString( info.window->GetClassInfo()->GetClassName() ).ToStdString(); + } + + if( !info.caption.IsEmpty() ) + meta["caption"] = info.caption.ToStdString(); + + meta["toolbar"] = info.IsToolbar(); + meta["center"] = ( info.dock_direction == wxAUI_DOCK_CENTER ); + meta["notebook"] = true; + + m_currentNotebook["meta"] = std::move( meta ); + m_currentNotebook["tabs"] = nlohmann::json::array(); + } + + void SaveNotebookTabControl( const wxAuiTabLayoutInfo& aTab ) override + { + nlohmann::json tab = nlohmann::json::object(); + + addDockLayout( tab, aTab ); + + if( !aTab.pages.empty() ) + tab["pages"] = aTab.pages; + + if( !aTab.pinned.empty() ) + tab["pinned"] = aTab.pinned; + + if( aTab.active >= 0 ) + tab["active"] = aTab.active; + + m_currentNotebook["tabs"].push_back( std::move( tab ) ); + } + + void AfterSaveNotebook() override + { + m_notebooks.push_back( std::move( m_currentNotebook ) ); + m_currentNotebook = nlohmann::json(); + } + + void AfterSaveNotebooks() override + { + if( !m_notebooks.empty() ) + m_root["notebooks"] = std::move( m_notebooks ); + } + + void AfterSave() override {} + +private: + wxAuiManager& m_manager; + nlohmann::json m_root; + nlohmann::json m_panes; + nlohmann::json m_notebooks; + nlohmann::json m_currentNotebook; + size_t m_paneIndex; +}; + +class JSON_DESERIALIZER : public wxAuiDeserializer +{ +public: + JSON_DESERIALIZER( wxAuiManager& aManager, const nlohmann::json& aState ) + : wxAuiDeserializer( aManager ), m_manager( aManager ), m_state( aState ) + { + if( !m_state.is_object() ) + throw std::runtime_error( "Invalid AUI layout state" ); + + const std::string format = m_state.value( "format", std::string() ); + + if( format != "kicad.wxaui" ) + throw std::runtime_error( "Unsupported AUI layout format" ); + + int version = m_state.value( "version", 0 ); + + if( version != 1 ) + throw std::runtime_error( "Unsupported AUI layout version" ); + + if( m_state.contains( "panes" ) && m_state["panes"].is_array() ) + m_serializedPanes = m_state["panes"].get>(); + + if( m_state.contains( "notebooks" ) && m_state["notebooks"].is_array() ) + m_serializedNotebooks = m_state["notebooks"].get>(); + } + + std::vector LoadPanes() override + { + std::vector panes; + + wxAuiPaneInfoArray paneArray = m_manager.GetAllPanes(); + + std::vector metadata; + metadata.reserve( paneArray.GetCount() ); + + for( size_t i = 0; i < paneArray.GetCount(); ++i ) + metadata.push_back( buildMetadata( paneArray[i], i ) ); + + std::set used; + + for( const nlohmann::json& jsonPane : m_serializedPanes ) + { + if( !jsonPane.is_object() ) + continue; + + wxAuiPaneLayoutInfo pane( wxString::FromUTF8( jsonPane.value( "name", std::string() ) ) ); + + readDockLayout( jsonPane.value( "dock", nlohmann::json::object() ), pane ); + + if( jsonPane.contains( "floating" ) ) + { + const nlohmann::json& floating = jsonPane["floating"]; + + if( floating.contains( "rect" ) ) + { + wxRect rect = floating["rect"].get(); + pane.floating_pos = rect.GetPosition(); + pane.floating_size = rect.GetSize(); + } + } + + pane.is_maximized = jsonPane.value( "maximized", false ); + pane.is_hidden = jsonPane.value( "hidden", false ); + + wxAuiPaneInfo* actualPane = matchPane( jsonPane, metadata, used ); + + if( actualPane ) + { + pane.name = actualPane->name; + used.insert( actualPane ); + panes.push_back( pane ); + } + } + + return panes; + } + + std::vector LoadNotebookTabs( const wxString& aName ) override + { + const wxAuiPaneInfo& paneInfo = m_manager.GetPane( aName ); + + auto loadTabs = []( const nlohmann::json& aNotebook ) + { + std::vector tabs; + + if( !aNotebook.contains( "tabs" ) || !aNotebook["tabs"].is_array() ) + return tabs; + + for( const nlohmann::json& tabJson : aNotebook["tabs"].get>() ) + { + if( !tabJson.is_object() ) + continue; + + wxAuiTabLayoutInfo info; + + readDockLayout( tabJson.value( "dock", nlohmann::json::object() ), info ); + + if( tabJson.contains( "pages" ) ) + { + info.pages.clear(); + for( int page : tabJson["pages"].get>() ) + info.pages.push_back( page ); + } + + if( tabJson.contains( "pinned" ) ) + { + info.pinned.clear(); + for( int page : tabJson["pinned"].get>() ) + info.pinned.push_back( page ); + } + + info.active = tabJson.value( "active", info.active ); + + tabs.push_back( info ); + } + + return tabs; + }; + + for( const nlohmann::json& notebook : m_serializedNotebooks ) + { + if( !notebook.is_object() ) + continue; + + wxString storedName = wxString::FromUTF8( notebook.value( "name", std::string() ) ); + + if( storedName == aName ) + return loadTabs( notebook ); + } + + if( !paneInfo.IsOk() ) + return {}; + + wxString windowName = paneInfo.window ? getWindowName( paneInfo.window ) : wxString(); + wxString className; + + if( paneInfo.window && paneInfo.window->GetClassInfo() ) + className = paneInfo.window->GetClassInfo()->GetClassName(); + + for( const nlohmann::json& notebook : m_serializedNotebooks ) + { + if( !notebook.is_object() ) + continue; + + const nlohmann::json& meta = notebook.value( "meta", nlohmann::json::object() ); + + const wxString storedWindowName = wxString::FromUTF8( meta.value( "window_name", std::string() ) ); + const wxString storedClassName = wxString::FromUTF8( meta.value( "class_name", std::string() ) ); + + const bool toolbar = meta.value( "toolbar", false ); + const bool center = meta.value( "center", false ); + const bool notebookFlag = meta.value( "notebook", false ); + + if( toolbar != paneInfo.IsToolbar() || center != ( paneInfo.dock_direction == wxAUI_DOCK_CENTER ) ) + continue; + + if( !notebookFlag ) + continue; + + if( !windowName.IsEmpty() && !storedWindowName.IsEmpty() && windowName == storedWindowName ) + return loadTabs( notebook ); + + if( !className.IsEmpty() && !storedClassName.IsEmpty() && className == storedClassName ) + return loadTabs( notebook ); + } + + return {}; + } + + wxWindow* CreatePaneWindow( wxAuiPaneInfo& ) override + { + return nullptr; + } + +private: + wxAuiPaneInfo* matchPane( const nlohmann::json& aJson, + const std::vector& aMetadata, + const std::set& aUsed ) const + { + auto selectCandidate = [&aUsed]( wxAuiPaneInfo* pane ) -> bool + { + return pane && aUsed.find( pane ) == aUsed.end(); + }; + + wxString storedName = wxString::FromUTF8( aJson.value( "name", std::string() ) ); + + if( !storedName.IsEmpty() ) + { + wxAuiPaneInfo& pane = m_manager.GetPane( storedName ); + + if( pane.IsOk() && selectCandidate( &pane ) ) + return &pane; + } + + wxAuiPaneInfo* match = nullptr; + const nlohmann::json& meta = aJson.value( "meta", nlohmann::json::object() ); + + const wxString windowName = wxString::FromUTF8( meta.value( "window_name", std::string() ) ); + const wxString className = wxString::FromUTF8( meta.value( "class_name", std::string() ) ); + const wxString caption = wxString::FromUTF8( meta.value( "caption", std::string() ) ); + const bool isToolbar = meta.value( "toolbar", false ); + const bool isCenter = meta.value( "center", false ); + const bool isNotebook = meta.value( "notebook", false ); + const size_t index = meta.value( "index", std::numeric_limits::max() ); + + auto findBy = [&]( auto predicate ) -> wxAuiPaneInfo* + { + wxAuiPaneInfo* candidate = nullptr; + + for( const PANE_METADATA& data : aMetadata ) + { + if( !predicate( data ) ) + continue; + + if( !selectCandidate( data.paneInfo ) ) + continue; + + if( candidate ) + return nullptr; + + candidate = data.paneInfo; + } + + return candidate; + }; + + if( !windowName.IsEmpty() ) + { + match = findBy( [&]( const PANE_METADATA& data ) + { + return data.windowName == windowName; + } ); + + if( match ) + return match; + } + + if( !className.IsEmpty() ) + { + match = findBy( [&]( const PANE_METADATA& data ) + { + if( data.className != className ) + return false; + + if( data.isToolbar != isToolbar ) + return false; + + if( data.isCenter != isCenter ) + return false; + + if( data.isNotebook != isNotebook ) + return false; + + return true; + } ); + + if( match ) + return match; + } + + if( index != std::numeric_limits::max() ) + { + match = findBy( [&]( const PANE_METADATA& data ) + { + return data.index == index; + } ); + + if( match ) + return match; + } + + if( !caption.IsEmpty() ) + { + match = findBy( [&]( const PANE_METADATA& data ) + { + return data.caption == caption; + } ); + + if( match ) + return match; + } + + match = findBy( [&]( const PANE_METADATA& data ) + { + if( data.isToolbar != isToolbar ) + return false; + + if( data.isNotebook != isNotebook ) + return false; + + return true; + } ); + + return match; + } + + wxAuiManager& m_manager; + nlohmann::json m_state; + std::vector m_serializedPanes; + std::vector m_serializedNotebooks; +}; + +#endif +} // namespace + +WX_AUI_JSON_SERIALIZER::WX_AUI_JSON_SERIALIZER( wxAuiManager& aManager ) : m_manager( aManager ) +{ +} + +nlohmann::json WX_AUI_JSON_SERIALIZER::Serialize() const +{ +#if wxCHECK_VERSION( 3, 3, 0 ) + try + { + JSON_SERIALIZER serializer( m_manager ); + m_manager.SaveLayout( serializer ); + return serializer.GetState(); + } + catch( const std::exception& err ) + { + wxLogWarning( "Failed to serialize AUI layout: %s", err.what() ); + } +#endif + + return nlohmann::json(); +} + +bool WX_AUI_JSON_SERIALIZER::Deserialize( const nlohmann::json& aState ) const +{ +#if wxCHECK_VERSION( 3, 3, 0 ) + if( aState.is_null() || aState.empty() ) + return false; + + try + { + JSON_DESERIALIZER deserializer( m_manager, aState ); + m_manager.LoadLayout( deserializer ); + return true; + } + catch( const std::exception& err ) + { + wxLogWarning( "Failed to deserialize AUI layout: %s", err.what() ); + } +#endif + + return false; +} diff --git a/cvpcb/display_footprints_frame.cpp b/cvpcb/display_footprints_frame.cpp index 87953d62b0..337e8a00fb 100644 --- a/cvpcb/display_footprints_frame.cpp +++ b/cvpcb/display_footprints_frame.cpp @@ -136,6 +136,7 @@ DISPLAY_FOOTPRINTS_FRAME::DISPLAY_FOOTPRINTS_FRAME( KIWAY* aKiway, wxWindow* aPa m_auimgr.AddPane( m_messagePanel, EDA_PANE().Messages().Name( wxS( "MsgPanel" ) ) .Bottom().Layer( 6 ) ); + RestoreAuiLayout(); FinishAUIInitialization(); auto& galOpts = GetGalDisplayOptions(); diff --git a/eeschema/sch_edit_frame.cpp b/eeschema/sch_edit_frame.cpp index 77af0b474b..a3020d625d 100644 --- a/eeschema/sch_edit_frame.cpp +++ b/eeschema/sch_edit_frame.cpp @@ -277,6 +277,7 @@ SCH_EDIT_FRAME::SCH_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : .DestroyOnClose( false ) .Show( m_show_search ) ); + RestoreAuiLayout(); FinishAUIInitialization(); wxAuiPaneInfo& hierarchy_pane = m_auimgr.GetPane( SchematicHierarchyPaneName() ); diff --git a/eeschema/symbol_editor/symbol_edit_frame.cpp b/eeschema/symbol_editor/symbol_edit_frame.cpp index abc8bbecb0..12d5fcd601 100644 --- a/eeschema/symbol_editor/symbol_edit_frame.cpp +++ b/eeschema/symbol_editor/symbol_edit_frame.cpp @@ -218,11 +218,12 @@ SYMBOL_EDIT_FRAME::SYMBOL_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : .MinSize( FromDIP( 250 ), -1 ).BestSize( FromDIP( 250 ), -1 ) ); m_auimgr.AddPane( m_propertiesPanel, defaultPropertiesPaneInfo( this ) ); - // Show or hide m_propertiesPanel depending on current settings: - wxAuiPaneInfo& propertiesPaneInfo = m_auimgr.GetPane( PropertiesPaneName() ); - m_auimgr.AddPane( m_selectionFilterPanel, defaultSchSelectionFilterPaneInfo( this ) ); + RestoreAuiLayout(); + + // Show or hide m_propertiesPanel depending on current settings: + wxAuiPaneInfo& propertiesPaneInfo = m_auimgr.GetPane( PropertiesPaneName() ); wxAuiPaneInfo& selectionFilterPane = m_auimgr.GetPane( wxS( "SelectionFilter" ) ); // The selection filter doesn't need to grow in the vertical direction when docked selectionFilterPane.dock_proportion = 0; diff --git a/eeschema/symbol_viewer_frame.cpp b/eeschema/symbol_viewer_frame.cpp index f582363f47..b37fa0d91a 100644 --- a/eeschema/symbol_viewer_frame.cpp +++ b/eeschema/symbol_viewer_frame.cpp @@ -217,6 +217,7 @@ SYMBOL_VIEWER_FRAME::SYMBOL_VIEWER_FRAME( KIWAY* aKiway, wxWindow* aParent ) : m_auimgr.AddPane( GetCanvas(), EDA_PANE().Canvas().Name( "DrawFrame" ).Center() ); + RestoreAuiLayout(); m_auimgr.Update(); if( m_libListWidth > 0 ) diff --git a/gerbview/CMakeLists.txt b/gerbview/CMakeLists.txt index d33d8cbcfe..aa7c32eb48 100644 --- a/gerbview/CMakeLists.txt +++ b/gerbview/CMakeLists.txt @@ -156,6 +156,7 @@ target_link_libraries( gerbview_kiface PRIVATE nlohmann_json gal + kicommon common core gerbview_kiface_objects diff --git a/include/eda_base_frame.h b/include/eda_base_frame.h index cf78564f90..57a0583384 100644 --- a/include/eda_base_frame.h +++ b/include/eda_base_frame.h @@ -37,6 +37,8 @@ #include #include +#include + #include #include #include @@ -238,6 +240,8 @@ public: void CreateInfoBar(); + void RestoreAuiLayout(); + void FinishAUIInitialization(); /** @@ -802,6 +806,7 @@ private: wxAuiManager m_auimgr; wxString m_perspective; // wxAuiManager perspective. + nlohmann::json m_auiLayoutState; WX_INFOBAR* m_infoBar; // Infobar for the frame APPEARANCE_CONTROLS_3D* m_appearancePanel; wxString m_configName; // Prefix used to identify some params (frame diff --git a/include/settings/app_settings.h b/include/settings/app_settings.h index d9055ff91f..d81942b30b 100644 --- a/include/settings/app_settings.h +++ b/include/settings/app_settings.h @@ -22,6 +22,7 @@ #define _APP_SETTINGS_H #include +#include #include #include @@ -97,6 +98,7 @@ struct KICOMMON_API WINDOW_SETTINGS WINDOW_STATE state; wxString mru_path; wxString perspective; + nlohmann::json aui_state; std::vector zoom_factors; CURSOR_SETTINGS cursor; diff --git a/include/widgets/aui_json_serializer.h b/include/widgets/aui_json_serializer.h new file mode 100644 index 0000000000..62306ad9d0 --- /dev/null +++ b/include/widgets/aui_json_serializer.h @@ -0,0 +1,40 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * 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 3 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, see . + */ + +#ifndef KICAD_WIDGETS_AUI_JSON_SERIALIZER_H +#define KICAD_WIDGETS_AUI_JSON_SERIALIZER_H + +#include +#include + +class wxAuiManager; + +class KICOMMON_API WX_AUI_JSON_SERIALIZER +{ +public: + explicit WX_AUI_JSON_SERIALIZER( wxAuiManager& aManager ); + + nlohmann::json Serialize() const; + bool Deserialize( const nlohmann::json& aState ) const; + +private: + wxAuiManager& m_manager; +}; + +#endif diff --git a/pcbnew/footprint_edit_frame.cpp b/pcbnew/footprint_edit_frame.cpp index a8ea1370e3..8e6eb81302 100644 --- a/pcbnew/footprint_edit_frame.cpp +++ b/pcbnew/footprint_edit_frame.cpp @@ -250,6 +250,8 @@ FOOTPRINT_EDIT_FRAME::FOOTPRINT_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : m_auimgr.AddPane( GetCanvas(), EDA_PANE().Canvas().Name( "DrawFrame" ) .Center() ); + RestoreAuiLayout(); + m_auimgr.GetPane( "LayersManager" ).Show( m_show_layer_manager_tools ); m_auimgr.GetPane( "SelectionFilter" ).Show( m_show_layer_manager_tools ); m_auimgr.GetPane( PropertiesPaneName() ).Show( GetSettings()->m_AuiPanels.show_properties ); diff --git a/pcbnew/footprint_wizard_frame.cpp b/pcbnew/footprint_wizard_frame.cpp index 3a6fb5d49f..2744729988 100644 --- a/pcbnew/footprint_wizard_frame.cpp +++ b/pcbnew/footprint_wizard_frame.cpp @@ -36,8 +36,11 @@ #include #include #include +#include #include #include + +#include #include #include #include @@ -509,6 +512,7 @@ void FOOTPRINT_WIZARD_FRAME::LoadSettings( APP_SETTINGS_BASE* aCfg ) PCB_BASE_FRAME::LoadSettings( cfg ); m_auiPerspective = cfg->m_FootprintViewer.perspective; + m_viewerAuiState = cfg->m_FootprintViewer.aui_state; } @@ -519,7 +523,22 @@ void FOOTPRINT_WIZARD_FRAME::SaveSettings( APP_SETTINGS_BASE* aCfg ) PCB_BASE_FRAME::SaveSettings( cfg ); +#if wxCHECK_VERSION( 3, 3, 0 ) + { + WX_AUI_JSON_SERIALIZER serializer( m_auimgr ); + nlohmann::json state = serializer.Serialize(); + + if( state.is_null() || state.empty() ) + cfg->m_FootprintViewer.aui_state = nlohmann::json(); + else + cfg->m_FootprintViewer.aui_state = state; + + cfg->m_FootprintViewer.perspective.clear(); + } +#else cfg->m_FootprintViewer.perspective = m_auimgr.SavePerspective().ToStdString(); + cfg->m_FootprintViewer.aui_state = nlohmann::json(); +#endif } diff --git a/pcbnew/footprint_wizard_frame.h b/pcbnew/footprint_wizard_frame.h index 9244385437..e4ec3a0cb0 100644 --- a/pcbnew/footprint_wizard_frame.h +++ b/pcbnew/footprint_wizard_frame.h @@ -33,6 +33,7 @@ #include #include #include +#include class wxSashLayoutWindow; class wxSashEvent; @@ -191,6 +192,7 @@ private: wxTextCtrl* m_buildMessageBox; wxString m_auiPerspective; ///< Encoded string describing the AUI layout + nlohmann::json m_viewerAuiState; bool m_wizardListShown; ///< A show-once flag for the wizard list }; diff --git a/pcbnew/pcb_edit_frame.cpp b/pcbnew/pcb_edit_frame.cpp index 3087af8117..1feceaf87b 100644 --- a/pcbnew/pcb_edit_frame.cpp +++ b/pcbnew/pcb_edit_frame.cpp @@ -378,6 +378,7 @@ PCB_EDIT_FRAME::PCB_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : .DestroyOnClose( false ) .CloseButton( true ) ); + RestoreAuiLayout(); m_auimgr.GetPane( "LayersManager" ).Show( m_show_layer_manager_tools ); m_auimgr.GetPane( "SelectionFilter" ).Show( m_show_layer_manager_tools ); @@ -388,7 +389,6 @@ PCB_EDIT_FRAME::PCB_EDIT_FRAME( KIWAY* aKiway, wxWindow* aParent ) : // The selection filter doesn't need to grow in the vertical direction when docked m_auimgr.GetPane( "SelectionFilter" ).dock_proportion = 0; - FinishAUIInitialization(); if( aui_cfg.right_panel_width > 0 )