Merge branch 'aui-serialize'

Provide a way to store AUI layouts in any custom format.

Closes #24225.

See #24235.
This commit is contained in:
Vadim Zeitlin
2024-10-16 21:14:25 +02:00
13 changed files with 947 additions and 10 deletions
+1
View File
@@ -4018,6 +4018,7 @@ COND_USE_GUI_1_ALL_GUI_HEADERS = \
wx/aui/tabart.h \
wx/xrc/xh_aui.h \
wx/xrc/xh_auitoolb.h \
wx/aui/serializer.h \
$(AUI_GTK_HDR) \
$(AUI_PLATFORM_HDR) \
wx/propgrid/advprops.h \
+1
View File
@@ -3006,6 +3006,7 @@ IMPORTANT: please read docs/tech/tn0016.txt before modifying this file!
wx/aui/tabart.h
wx/xrc/xh_aui.h
wx/xrc/xh_auitoolb.h
wx/aui/serializer.h
</set>
<set var="AUI_HDR" hints="files">
$(AUI_CMN_HDR)
+1
View File
@@ -2793,6 +2793,7 @@ set(AUI_CMN_HDR
wx/aui/tabart.h
wx/xrc/xh_aui.h
wx/xrc/xh_auitoolb.h
wx/aui/serializer.h
)
set(AUI_MSW_HDR
+1 -1
View File
@@ -11,7 +11,7 @@ wx_add_sample(access accesstest.cpp DEPENDS wxUSE_ACCESSIBILITY)
wx_add_sample(animate anitest.cpp anitest.h DATA throbber.gif throbber_2x.gif hourglass.ani DEPENDS wxUSE_ANIMATIONCTRL)
wx_add_sample(archive CONSOLE)
wx_add_sample(artprov arttest.cpp artbrows.cpp artbrows.h)
wx_add_sample(aui auidemo.cpp LIBRARIES wxaui wxhtml NAME auidemo DEPENDS wxUSE_AUI)
wx_add_sample(aui auidemo.cpp LIBRARIES wxaui wxhtml wxxml NAME auidemo DEPENDS wxUSE_AUI)
wx_add_sample(calendar DEPENDS wxUSE_CALENDARCTRL)
wx_add_sample(caret DEPENDS wxUSE_CARET)
wx_add_sample(collpane DEPENDS wxUSE_COLLPANE)
+1
View File
@@ -2722,6 +2722,7 @@ AUI_CMN_HDR =
wx/aui/auibar.h
wx/aui/tabmdi.h
wx/aui/aui.h
wx/aui/serializer.h
wx/aui/tabart.h
wx/xrc/xh_aui.h
wx/xrc/xh_auitoolb.h
+1
View File
@@ -931,6 +931,7 @@
<ClInclude Include="..\..\include\wx\aui\tabartmsw.h" />
<ClInclude Include="..\..\include\wx\aui\barartmsw.h" />
<ClInclude Include="..\..\include\wx\xrc\xh_aui.h" />
<ClInclude Include="..\..\include\wx\aui\serializer.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
+3
View File
@@ -86,6 +86,9 @@
<ClInclude Include="..\..\include\wx\aui\framemanager.h">
<Filter>Common Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\include\wx\aui\serializer.h">
<Filter>Common Headers</Filter>
</ClInclude>
<ClInclude Include="..\..\include\wx\aui\tabart.h">
<Filter>Common Headers</Filter>
</ClInclude>
+8
View File
@@ -128,6 +128,8 @@ class wxAuiPaneInfo;
class wxAuiDockInfo;
class wxAuiDockArt;
class wxAuiManagerEvent;
class wxAuiSerializer;
class wxAuiDeserializer;
using wxAuiDockUIPartArray = wxBaseArray<wxAuiDockUIPart>;
using wxAuiDockInfoArray = wxBaseArray<wxAuiDockInfo>;
@@ -452,6 +454,12 @@ public:
void Update();
// Serialize or restore the whole layout using the provided serializer.
void SaveLayout(wxAuiSerializer& serializer) const;
void LoadLayout(wxAuiDeserializer& deserializer);
// Older functions using bespoke text format, prefer using the ones using
// wxAuiSerializer and wxAuiDeserializer above instead in the new code.
wxString SavePaneInfo(const wxAuiPaneInfo& pane);
void LoadPaneInfo(wxString panePart, wxAuiPaneInfo &pane);
wxString SavePerspective();
+109
View File
@@ -0,0 +1,109 @@
///////////////////////////////////////////////////////////////////////////////
// Name: wx/aui/serializer.h
// Purpose: Declaration of wxAuiSerializer and wxAuiDeserializer classes.
// Author: Vadim Zeitlin
// Created: 2024-01-20
// Copyright: (c) 2024 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
///////////////////////////////////////////////////////////////////////////////
#ifndef _WX_AUI_SERIALIZER_H_
#define _WX_AUI_SERIALIZER_H_
// ----------------------------------------------------------------------------
// Classes used to save/load wxAuiManager layout.
// ----------------------------------------------------------------------------
// wxAuiSerializer is used with wxAuiManager::SaveLayout().
//
// This is an abstract base class, you need to inherit from it and override its
// pure virtual functions in your derived class.
//
// If any of the functions of the derived class throw an exception, it is
// propagated out of wxAuiManager::SaveLayout() and it's callers responsibility
// to handle it.
class wxAuiSerializer
{
public:
// Trivial default ctor.
wxAuiSerializer() = default;
// Trivial but virtual dtor for a base class.
virtual ~wxAuiSerializer() = default;
// Called before doing anything else, does nothing by default.
virtual void BeforeSave() { }
// Called before starting to save information about the panes, does nothing
// by default.
virtual void BeforeSavePanes() { }
// Save information about the given pane.
virtual void SavePane(const wxAuiPaneInfo& pane) = 0;
// Called after the last call to SavePane(), does nothing by default.
virtual void AfterSavePanes() { }
// Called before starting to save information about the docks, does nothing
// by default.
virtual void BeforeSaveDocks() { }
// Save information about the given dock.
virtual void SaveDock(const wxAuiDockInfo& dock) = 0;
// Called after the last call to SaveDock(), does nothing by default.
virtual void AfterSaveDocks() { }
// Called after saving everything, does nothing by default.
virtual void AfterSave() { }
};
// wxAuiDeserializer is used with wxAuiManager::LoadLayout().
//
// As wxAuiSerializer, this is an abstract base class, you need to inherit from
// it and override its pure virtual functions in your derived class.
//
// Derived class function also may throw and, if any of them other than
// AfterLoad() does, the existing layout is not changed, i.e.
// wxAuiManager::LoadLayout() is exception-safe.
class wxAuiDeserializer
{
public:
// Ctor takes the manager for which we're restoring the layout, it must
// remain valid for the lifetime of this object.
explicit wxAuiDeserializer(wxAuiManager& manager) : m_manager(manager) { }
// Trivial but virtual dtor for a base class.
virtual ~wxAuiDeserializer() = default;
// Called before doing anything else, does nothing by default.
virtual void BeforeLoad() { }
// Load information about all the panes previously saved with SavePane().
virtual std::vector<wxAuiPaneInfo> LoadPanes() = 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.
//
// If this function returns nullptr, the pane is not added to the manager.
virtual wxWindow* CreatePaneWindow(const wxAuiPaneInfo& WXUNUSED(pane))
{
return nullptr;
}
// Load information about all the docks previously saved with SaveDock().
virtual std::vector<wxAuiDockInfo> LoadDocks() = 0;
// Called after restoring everything, calls Update() on the manager by
// default.
virtual void AfterLoad() { m_manager.Update(); }
protected:
// The manager for which we're restoring the layout.
wxAuiManager& m_manager;
};
#endif // _WX_AUI_SERIALIZER_H_
+33
View File
@@ -371,6 +371,19 @@ public:
const wxAuiPaneInfo& insert_location,
int insert_level = wxAUI_INSERT_PANE);
/**
Load the layout information saved by SaveLayout().
The implementation of wxAuiDeserializer object passed to this function
should be consistent with that of the serializer used to save the
layout. See @ref page_samples_aui for an example of using serializer
saving the layout in XML format and matching deserializer restoring the
layout from it.
@since 3.3.0
*/
void LoadLayout(wxAuiDeserializer& deserializer) const;
/**
LoadPaneInfo() is similar to LoadPerspective, with the exception that it
only loads information about a single pane.
@@ -389,6 +402,10 @@ public:
/**
Loads a saved perspective.
This function is used to load layouts previously saved with
SavePerspective(), use LoadLayout() to load a layout saved with
SaveLayout().
A perspective is the layout state of an AUI managed window.
All currently existing panes that have an object in "perspective"
@@ -422,6 +439,19 @@ public:
*/
void RestoreMaximizedPane();
/**
Save the layout information using the provided object.
This function allows to use a custom @a serializer to save the layout
information in any format, e.g. @ref page_samples_aui shows how to save
it in XML format.
See wxAuiSerializer documentation for more details.
@since 3.3.0
*/
void SaveLayout(wxAuiSerializer& serializer) const;
/**
SavePaneInfo() is similar to SavePerspective, with the exception that it only
saves information about a single pane.
@@ -441,6 +471,9 @@ public:
Saves the entire user interface layout into an encoded wxString, which
can then be stored by the application (probably using wxConfig).
@note You may prefer to use SaveLayout() instead of this function for
more flexibility.
@see LoadPerspective
@see LoadPaneInfo
@see SavePaneInfo
+202
View File
@@ -0,0 +1,202 @@
/////////////////////////////////////////////////////////////////////////////
// Name: aui/serializer.h
// Purpose: Documentation of wxAuiSerializer and wxAuiDeserializer.
// Author: Vadim Zeitlin
// Created: 2024-02-20
// Copyright: (c) 2024 Vadim Zeitlin <vadim@wxwidgets.org>
// Licence: wxWindows licence
/////////////////////////////////////////////////////////////////////////////
/**
@class wxAuiSerializer
wxAuiSerializer is used by wxAuiManager::SaveLayout() to store layout
information.
This is an abstract base class, you need to inherit from it and override its
pure virtual functions in your derived class.
SavePane() and SaveDock() must be overridden and will be called by
wxAuiManager for each pane and dock present in the layout. The other
functions don't need to be overridden, but it is often convenient to
perform some actions before or after starting to save the objects of the
given type or at the beginning or end of the whole saving process, so this
class provides hooks for doing it.
If any of the functions of the derived class throw an exception, it is
propagated out of wxAuiManager::SaveLayout() and it's callers responsibility
to handle it.
@library{wxaui}
@category{aui}
@since 3.3.0
*/
class wxAuiSerializer
{
public:
/// Trivial default ctor.
wxAuiSerializer() = default;
/// Trivial but virtual destructor.
virtual ~wxAuiSerializer() = default;
/**
Called before doing anything else.
Does nothing by default.
*/
virtual void BeforeSave();
/**
Called before starting to save information about the panes.
Does nothing by default.
*/
virtual void BeforeSavePanes();
/**
Save information about the given pane.
This function will be called for all panes and must be implemented to
save their data in a format from which it can be restored later using a
matching wxAuiDeserializer implementation.
Note that all sizes and positions in @a pane are using DIPs, i.e.
resolution-independent pixels, when it is passed to this function, so
it does _not_ need to perform any scaling itself to ensure that the
stored values are restored correctly if the resolution changes.
*/
virtual void SavePane(const wxAuiPaneInfo& pane) = 0;
/**
Called after the last call to SavePane().
Does nothing by default.
*/
virtual void AfterSavePanes();
/**
Called before starting to save information about the docks.
Does nothing by default.
*/
virtual void BeforeSaveDocks();
/**
Save information about the given dock.
This function will be called for all docks and must be implemented to
save their data in a format from which it can be restored later using a
matching wxAuiDeserializer implementation.
As with SavePane(), the coordinates in @a dock are always in DIPs and
this function does _not_ need to perform any scaling itself.
*/
virtual void SaveDock(const wxAuiDockInfo& dock) = 0;
/**
Called after the last call to SaveDock().
Does nothing by default.
*/
virtual void AfterSaveDocks();
/**
Called after saving everything.
Does nothing by default.
*/
virtual void AfterSave();
};
/**
@class wxAuiDeserializer
wxAuiDeserializer is used by wxAuiManager::LoadLayout() to restore layout
information saved by wxAuiManager::SaveLayout().
As wxAuiSerializer, this is an abstract base class, you need to inherit from
it and override its pure virtual functions in your derived class.
Derived class function also may throw and, if any of them other than
AfterLoad() does, the existing layout is not changed, i.e.
wxAuiManager::LoadLayout() is exception-safe.
@library{wxaui}
@category{aui}
@since 3.3.0
*/
class wxAuiDeserializer
{
public:
/**
Constructor taking the manager for which we're restoring the layout.
The manager remains valid for the lifetime of this object.
*/
explicit wxAuiDeserializer(wxAuiManager& manager);
/// Trivial but virtual destructor.
virtual ~wxAuiDeserializer() = default;
/**
Called before doing anything else.
Does nothing by default.
*/
virtual void BeforeLoad();
/**
Load information about all the panes previously saved by
wxAuiSerializer::SavePane().
Unlike the serializer function, this one is called only once and should
return all the panes in the layout.
Just as the serializer function, this one doesn't need to perform any
scaling itself as this will be done, if necessary, by wxAuiManager
itself.
If some pane in the returned vector doesn't already exist, i.e. there
is no pane with the matching name, CreatePaneWindow() is called to
allow creating it on the fly.
*/
virtual std::vector<wxAuiPaneInfo> LoadPanes() = 0;
/**
Create the window to be managed by the given pane if necessary.
This function 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.
If this function returns @NULL, as it does by default, the pane is not
added to the manager.
*/
virtual wxWindow* CreatePaneWindow(const wxAuiPaneInfo& pane);
/**
Load information about all the docks previously saved with SaveDock().
As with LoadPanes(), this function doesn't need to perform any scaling
itself.
*/
virtual std::vector<wxAuiDockInfo> LoadDocks() = 0;
/**
Called after restoring everything.
Default implementation calls wxAuiManager::Update(). Override this
function and do _not_ call the base class version if you want to
prevent this from happening, e.g. if you need to make further changes
to the restored layout before updating it.
*/
virtual void AfterLoad();
protected:
/// The manager for which we're restoring the layout.
wxAuiManager& m_manager;
};
+386 -9
View File
@@ -28,6 +28,7 @@
#include "wx/log.h"
#include "wx/menu.h"
#include "wx/toolbar.h"
#include "wx/sstream.h"
#include "wx/statusbr.h"
#include "wx/msgdlg.h"
#include "wx/textdlg.h"
@@ -35,6 +36,10 @@
#include "wx/checkbox.h"
#include "wx/aui/aui.h"
#include "wx/aui/serializer.h"
#include "wx/xml/xml.h"
#include "../sample.xpm"
// -- application --
@@ -70,7 +75,8 @@ class MyFrame : public wxFrame
ID_NotebookContent,
ID_SizeReportContent,
ID_CreatePerspective,
ID_CopyPerspectiveCode,
ID_CopyLayout,
ID_PasteLayout,
ID_AllowFloating,
ID_AllowActivePane,
ID_TransparentHint,
@@ -149,7 +155,8 @@ private:
void OnChangeContentPane(wxCommandEvent& evt);
void OnDropDownToolbarItem(wxAuiToolBarEvent& evt);
void OnCreatePerspective(wxCommandEvent& evt);
void OnCopyPerspectiveCode(wxCommandEvent& evt);
void OnCopyLayout(wxCommandEvent& evt);
void OnPasteLayout(wxCommandEvent& evt);
void OnRestorePerspective(wxCommandEvent& evt);
void OnSettings(wxCommandEvent& evt);
void OnCustomizeToolbar(wxCommandEvent& evt);
@@ -602,7 +609,8 @@ wxBEGIN_EVENT_TABLE(MyFrame, wxFrame)
EVT_MENU(MyFrame::ID_CreateSizeReport, MyFrame::OnCreateSizeReport)
EVT_MENU(MyFrame::ID_CreateNotebook, MyFrame::OnCreateNotebook)
EVT_MENU(MyFrame::ID_CreatePerspective, MyFrame::OnCreatePerspective)
EVT_MENU(MyFrame::ID_CopyPerspectiveCode, MyFrame::OnCopyPerspectiveCode)
EVT_MENU(MyFrame::ID_CopyLayout, MyFrame::OnCopyLayout)
EVT_MENU(MyFrame::ID_PasteLayout, MyFrame::OnPasteLayout)
EVT_MENU(ID_AllowFloating, MyFrame::OnManagerFlag)
EVT_MENU(ID_TransparentHint, MyFrame::OnManagerFlag)
EVT_MENU(ID_VenetianBlindsHint, MyFrame::OnManagerFlag)
@@ -764,7 +772,8 @@ MyFrame::MyFrame(wxWindow* parent,
m_perspectives_menu = new wxMenu;
m_perspectives_menu->Append(ID_CreatePerspective, _("Create Perspective"));
m_perspectives_menu->Append(ID_CopyPerspectiveCode, _("Copy Perspective Data To Clipboard"));
m_perspectives_menu->Append(ID_CopyLayout, _("Copy Layout to Clipboard as XML\tCtrl-C"));
m_perspectives_menu->Append(ID_PasteLayout, _("Paste XML Layout from Clipboard\tCtrl-V"));
m_perspectives_menu->AppendSeparator();
m_perspectives_menu->Append(ID_FirstPerspective+0, _("Default Startup"));
m_perspectives_menu->Append(ID_FirstPerspective+1, _("All Panes"));
@@ -1452,16 +1461,384 @@ void MyFrame::OnCreatePerspective(wxCommandEvent& WXUNUSED(event))
m_perspectives.Add(m_mgr.SavePerspective());
}
void MyFrame::OnCopyPerspectiveCode(wxCommandEvent& WXUNUSED(evt))
// Sample serializer and deserializer implementations for saving and loading layouts.
class MyXmlSerializer : public wxAuiSerializer
{
wxString s = m_mgr.SavePerspective();
public:
MyXmlSerializer() = default;
wxString GetXML() const
{
wxStringOutputStream oss;
if ( !m_doc.Save(oss) )
{
wxLogError("Failed to save XML document.");
}
return oss.GetString();
}
// Implement wxAuiSerializer methods.
virtual void BeforeSave() override
{
m_root = new wxXmlNode(wxXML_ELEMENT_NODE, "aui-layout");
m_root->AddAttribute("version", "3.3.0");
m_doc.SetRoot(m_root);
}
virtual void BeforeSavePanes() override
{
m_panes = new wxXmlNode(wxXML_ELEMENT_NODE, "panes");
}
virtual void SavePane(const wxAuiPaneInfo& pane) override
{
auto node = new wxXmlNode(m_panes, wxXML_ELEMENT_NODE, "pane");
node->AddAttribute("name", pane.name);
AddChild(node, "caption", pane.caption);
AddChild(node, "state", pane.state);
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);
AddChild(node, "best-size", pane.best_size);
AddChild(node, "min-size", pane.min_size);
AddChild(node, "max-size", pane.max_size);
AddChild(node, "floating-rect",
wxRect(pane.floating_pos, pane.floating_size));
}
virtual void AfterSavePanes() override
{
m_root->AddChild(m_panes);
}
virtual void BeforeSaveDocks() override
{
m_docks = new wxXmlNode(wxXML_ELEMENT_NODE, "docks");
}
virtual void SaveDock(const wxAuiDockInfo& dock) override
{
auto node = new wxXmlNode(m_docks, wxXML_ELEMENT_NODE, "dock");
node->AddAttribute("resizable", dock.resizable ? "1" : "0");
AddChild(node, "direction", dock.dock_direction);
AddChild(node, "layer", dock.dock_layer);
AddChild(node, "row", dock.dock_row);
AddChild(node, "size", dock.size);
if ( dock.min_size )
AddChild(node, "min-size", dock.min_size);
}
virtual void AfterSaveDocks() override
{
m_root->AddChild(m_docks);
}
virtual void AfterSave() override {}
private:
void AddChild(wxXmlNode* parent, const wxString& name, const wxString& value)
{
auto node = new wxXmlNode(parent, wxXML_ELEMENT_NODE, name);
node->AddChild(new wxXmlNode(wxXML_TEXT_NODE, {}, value));
}
void AddChild(wxXmlNode* parent, const wxString& name, int value)
{
AddChild(parent, name, wxString::Format("%u", value));
}
void AddChild(wxXmlNode* parent, const wxString& name, const wxSize& size)
{
if ( size != wxDefaultSize )
AddChild(parent, name, wxString::Format("%dx%d", size.x, size.y));
}
void AddChild(wxXmlNode* parent, const wxString& name, const wxRect& rect)
{
if ( rect.GetPosition() != wxDefaultPosition ||
rect.GetSize() != wxDefaultSize )
{
AddChild(parent, name,
wxString::Format("%d,%d %dx%d",
rect.x, rect.y, rect.width, rect.height));
}
}
wxXmlDocument m_doc;
wxXmlNode* m_root = nullptr;
wxXmlNode* m_panes = nullptr;
wxXmlNode* m_docks = nullptr;
};
class MyXmlDeserializer : public wxAuiDeserializer
{
public:
explicit MyXmlDeserializer(wxAuiManager& manager, const wxString& xml)
: wxAuiDeserializer(manager)
{
wxStringInputStream iss(xml);
if ( !m_doc.Load(iss) )
throw std::runtime_error("Failed to load XML document.");
const auto root = m_doc.GetDocumentNode();
const auto layout = root->GetChildren();
if ( !layout )
throw std::runtime_error("Missing layout node");
if ( layout->GetName() != "aui-layout" )
throw std::runtime_error("Unexpected XML node name");
if ( layout->GetAttribute("version") != "3.3.0" )
throw std::runtime_error("Unexpected XML version");
if ( layout->GetNext() )
throw std::runtime_error("Unexpected multiple layout nodes");
for ( wxXmlNode* node = layout->GetChildren(); node; node = node->GetNext() )
{
if ( node->GetName() == "panes" )
{
if ( m_panes )
throw std::runtime_error("Unexpected multiple panes nodes");
m_panes = node;
}
else if ( node->GetName() == "docks" )
{
if ( m_docks )
throw std::runtime_error("Unexpected multiple docks nodes");
m_docks = node;
}
else
{
throw std::runtime_error("Unexpected node name");
}
}
if ( !m_panes )
throw std::runtime_error("Missing panes node");
if ( !m_docks )
throw std::runtime_error("Missing docks node");
}
// Implement wxAuiDeserializer methods.
virtual std::vector<wxAuiPaneInfo> LoadPanes() override
{
std::vector<wxAuiPaneInfo> panes;
for ( wxXmlNode* node = m_panes->GetChildren(); node; node = node->GetNext() )
{
if ( node->GetName() != "pane" )
throw std::runtime_error("Unexpected pane node name");
{
wxAuiPaneInfo pane;
pane.name = node->GetAttribute("name");
for ( wxXmlNode* child = node->GetChildren(); child; child = child->GetNext() )
{
const wxString& name = child->GetName();
const wxString& content = child->GetNodeContent();
if ( name == "caption" )
{
pane.caption = content;
}
else if ( name == "state" )
{
pane.state = wxAuiPaneInfo::wxAuiPaneState(GetInt(content));
}
else 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 == "best-size" )
{
pane.best_size = GetSize(content);
}
else if ( name == "min-size" )
{
pane.min_size = GetSize(content);
}
else if ( name == "max-size" )
{
pane.max_size = GetSize(content);
}
else if ( name == "floating-rect" )
{
auto rect = GetRect(content);
pane.floating_pos = rect.GetPosition();
pane.floating_size = rect.GetSize();
}
else
{
throw std::runtime_error("Unexpected pane child node name");
}
}
panes.push_back(pane);
}
}
return panes;
}
virtual wxWindow* CreatePaneWindow(const wxAuiPaneInfo& pane) override
{
wxLogWarning("Unknown pane \"%s\"", pane.name);
return nullptr;
}
virtual std::vector<wxAuiDockInfo> LoadDocks() override
{
std::vector<wxAuiDockInfo> docks;
for ( wxXmlNode* node = m_docks->GetChildren(); node; node = node->GetNext() )
{
if ( node->GetName() != "dock" )
throw std::runtime_error("Unexpected dock node name");
wxAuiDockInfo dock;
if ( node->GetAttribute("resizable") == "1" )
dock.resizable = true;
for ( wxXmlNode* child = node->GetChildren(); child; child = child->GetNext() )
{
const wxString& name = child->GetName();
const wxString& content = child->GetNodeContent();
if ( name == "direction" )
{
dock.dock_direction = GetInt(content);
}
else if ( name == "layer" )
{
dock.dock_layer = GetInt(content);
}
else if ( name == "row" )
{
dock.dock_row = GetInt(content);
}
else if ( name == "size" )
{
dock.size = GetInt(content);
}
else if ( name == "min-size" )
{
dock.min_size = GetInt(content);
}
else
{
throw std::runtime_error("Unexpected dock child node name");
}
}
docks.push_back(dock);
}
return docks;
}
private:
int GetInt(const wxString& str)
{
int value;
if ( !str.ToInt(&value) )
throw std::runtime_error("Failed to parse integer");
return value;
}
wxSize GetSize(const wxString& str)
{
wxString strH;
const wxString strW = str.BeforeFirst('x', &strH);
unsigned int w, h;
if ( !strW.ToUInt(&w) || !strH.ToUInt(&h) )
throw std::runtime_error("Failed to parse size");
return wxSize(w, h);
}
wxRect GetRect(const wxString& str)
{
wxString strWH;
const wxString strXY = str.BeforeFirst(' ', &strWH);
wxString strY;
const wxString strX = strXY.BeforeFirst(',', &strY);
unsigned int x, y;
if ( !strX.ToUInt(&x) || !strY.ToUInt(&y) )
throw std::runtime_error("Failed to parse position");
return wxRect(wxPoint(x, y), GetSize(strWH));
}
wxXmlDocument m_doc;
wxXmlNode* m_panes = nullptr;
wxXmlNode* m_docks = nullptr;
};
void MyFrame::OnCopyLayout(wxCommandEvent& WXUNUSED(evt))
{
MyXmlSerializer ser;
m_mgr.SaveLayout(ser);
#if wxUSE_CLIPBOARD
if (wxTheClipboard->Open())
wxClipboardLocker clipLock;
wxTheClipboard->SetData(new wxTextDataObject(ser.GetXML()));
#endif // wxUSE_CLIPBOARD
}
void MyFrame::OnPasteLayout(wxCommandEvent& WXUNUSED(evt))
{
#if wxUSE_CLIPBOARD
wxClipboardLocker clipLock;
wxTextDataObject data;
if ( !wxTheClipboard->GetData(data) )
{
wxTheClipboard->SetData(new wxTextDataObject(s));
wxTheClipboard->Close();
wxLogError("Failed to get XML data from clipboard.");
return;
}
try
{
MyXmlDeserializer deser(m_mgr, data.GetText());
m_mgr.LoadLayout(deser);
}
catch ( const std::exception& e )
{
wxLogError("Failed to load layout: %s", e.what());
}
#else
wxLogError("This menu command requires wxUSE_CLIPBOARD.");
#endif
}
+200
View File
@@ -25,6 +25,7 @@
#include "wx/aui/floatpane.h"
#include "wx/aui/tabmdi.h"
#include "wx/aui/auibar.h"
#include "wx/aui/serializer.h"
#include "wx/mdi.h"
#include "wx/wupdlock.h"
@@ -1340,6 +1341,205 @@ bool wxAuiManager::LoadPerspective(const wxString& layout, bool update)
return true;
}
// These helper functions are used by SaveLayout() and LoadLayout() below, as
// we save the panes and docks geometries using DIPs on all platforms in order
// to ensure that they're restored correctly if the display DPI changes between
// saving and restoring the layout even on the platforms not using DIPs.
namespace
{
void MakeDIP(wxWindow* w, wxPoint& pos)
{
pos = w->ToDIP(pos);
}
void MakeDIP(wxWindow* w, wxSize& size)
{
size = w->ToDIP(size);
}
void MakeDIP(wxWindow* w, wxRect& rect)
{
rect = wxRect{w->ToDIP(rect.GetPosition()), w->ToDIP(rect.GetSize())};
}
void MakeLogical(wxWindow* w, wxPoint& pos)
{
pos = w->FromDIP(pos);
}
void MakeLogical(wxWindow* w, wxSize& size)
{
size = w->FromDIP(size);
}
void MakeLogical(wxWindow* w, wxRect& rect)
{
rect = wxRect{w->FromDIP(rect.GetPosition()), w->FromDIP(rect.GetSize())};
}
} // anonymous namespace
void wxAuiManager::SaveLayout(wxAuiSerializer& serializer) const
{
serializer.BeforeSave();
if ( !m_panes.empty() )
{
serializer.BeforeSavePanes();
for ( const auto& pane : m_panes )
{
auto paneDIP = pane;
MakeDIP(m_frame, paneDIP.best_size);
MakeDIP(m_frame, paneDIP.min_size);
MakeDIP(m_frame, paneDIP.max_size);
MakeDIP(m_frame, paneDIP.floating_pos);
MakeDIP(m_frame, paneDIP.floating_size);
MakeDIP(m_frame, paneDIP.rect);
serializer.SavePane(paneDIP);
}
serializer.AfterSavePanes();
}
if ( !m_docks.empty() )
{
serializer.BeforeSaveDocks();
for ( const auto& dock : m_docks )
{
auto dockDIP = dock;
MakeDIP(m_frame, dockDIP.rect);
// Update dock sizes to ensure that restoring this layout later
// restores the same geometry as is used now: if we didn't do it,
// panes would have their initial sizes.
switch ( dock.dock_direction )
{
case wxAUI_DOCK_TOP:
case wxAUI_DOCK_BOTTOM:
dockDIP.size = dock.rect.height;
break;
case wxAUI_DOCK_LEFT:
case wxAUI_DOCK_RIGHT:
dockDIP.size = dock.rect.width;
break;
case wxAUI_DOCK_CENTER:
// Not clear what to do for this one, but it shouldn't
// matter as its size is determined by what remains
// available after positioning the rest of the elements, so
// don't do anything.
break;
case wxAUI_DOCK_NONE:
wxFAIL_MSG("invalid dock direction");
break;
}
serializer.SaveDock(dockDIP);
}
serializer.AfterSaveDocks();
}
serializer.AfterSave();
}
void wxAuiManager::LoadLayout(wxAuiDeserializer& deserializer)
{
deserializer.BeforeLoad();
// Note that we don't reset m_hasMaximized here but use a local variable so
// as to avoid modifying m_hasMaximized in case one of the deserializer
// functions called below throws an exception.
bool hasMaximized = false;
// Also keep local variables for the existing (and possibly updated) panes
// and the new ones for the same reason.
wxAuiPaneInfoArray panes = m_panes;
struct NewPane
{
// In C++11 this ctor is required.
NewPane(wxWindow* window_, const wxAuiPaneInfo& info_)
: window(window_), info(info_)
{
}
wxWindow* window = nullptr;
wxAuiPaneInfo info;
};
std::vector<NewPane> newPanes;
for ( const auto& paneDIP : deserializer.LoadPanes() )
{
auto pane = paneDIP;
MakeLogical(m_frame, pane.best_size);
MakeLogical(m_frame, pane.min_size);
MakeLogical(m_frame, pane.max_size);
MakeLogical(m_frame, pane.floating_pos);
MakeLogical(m_frame, pane.floating_size);
MakeLogical(m_frame, pane.rect);
if ( pane.IsMaximized() )
hasMaximized = true;
// Find the pane with the same name in the existing layout.
bool found = false;
for ( auto& existingPane : panes )
{
if ( existingPane.name == pane.name )
{
// Update the existing pane with the restored layout.
existingPane.SafeSet(pane);
found = true;
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 ( const auto w = deserializer.CreatePaneWindow(pane) )
newPanes.emplace_back(w, pane);
}
}
wxAuiDockInfoArray docks;
for ( const auto& dockDIP : deserializer.LoadDocks() )
{
auto dock = dockDIP;
MakeLogical(m_frame, dock.rect);
docks.push_back(dock);
}
// After loading everything successfully, do update the internal variables.
m_hasMaximized = hasMaximized;
m_panes.swap(panes);
for ( const auto& newPane : newPanes )
AddPane(newPane.window, newPane.info);
m_docks.swap(docks);
deserializer.AfterLoad();
}
void wxAuiManager::GetPanePositionsAndSizes(wxAuiDockInfo& dock,
wxArrayInt& positions,
wxArrayInt& sizes)