You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
623 lines
19 KiB
623 lines
19 KiB
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <widgets/aui_json_serializer.h>
|
|
|
|
#include <settings/aui_settings.h>
|
|
|
|
#include <wx/aui/framemanager.h>
|
|
#if wxCHECK_VERSION( 3, 3, 0 )
|
|
#include <wx/aui/serializer.h>
|
|
#include <wx/aui/auibook.h>
|
|
#endif
|
|
#include <wx/log.h>
|
|
#include <wx/window.h>
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
#include <algorithm>
|
|
#include <limits>
|
|
#include <set>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
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<std::vector<nlohmann::json>>();
|
|
|
|
if( m_state.contains( "notebooks" ) && m_state["notebooks"].is_array() )
|
|
m_serializedNotebooks = m_state["notebooks"].get<std::vector<nlohmann::json>>();
|
|
}
|
|
|
|
std::vector<wxAuiPaneLayoutInfo> LoadPanes() override
|
|
{
|
|
std::vector<wxAuiPaneLayoutInfo> panes;
|
|
|
|
wxAuiPaneInfoArray paneArray = m_manager.GetAllPanes();
|
|
|
|
std::vector<PANE_METADATA> metadata;
|
|
metadata.reserve( paneArray.GetCount() );
|
|
|
|
for( size_t i = 0; i < paneArray.GetCount(); ++i )
|
|
metadata.push_back( buildMetadata( paneArray[i], i ) );
|
|
|
|
std::set<wxAuiPaneInfo*> 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<wxRect>();
|
|
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<wxAuiTabLayoutInfo> LoadNotebookTabs( const wxString& aName ) override
|
|
{
|
|
const wxAuiPaneInfo& paneInfo = m_manager.GetPane( aName );
|
|
|
|
auto loadTabs = []( const nlohmann::json& aNotebook )
|
|
{
|
|
std::vector<wxAuiTabLayoutInfo> tabs;
|
|
|
|
if( !aNotebook.contains( "tabs" ) || !aNotebook["tabs"].is_array() )
|
|
return tabs;
|
|
|
|
for( const nlohmann::json& tabJson : aNotebook["tabs"].get<std::vector<nlohmann::json>>() )
|
|
{
|
|
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<std::vector<int>>() )
|
|
info.pages.push_back( page );
|
|
}
|
|
|
|
if( tabJson.contains( "pinned" ) )
|
|
{
|
|
info.pinned.clear();
|
|
for( int page : tabJson["pinned"].get<std::vector<int>>() )
|
|
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<PANE_METADATA>& aMetadata,
|
|
const std::set<wxAuiPaneInfo*>& 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<size_t>::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<size_t>::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<nlohmann::json> m_serializedPanes;
|
|
std::vector<nlohmann::json> 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;
|
|
}
|