KiCad PCB EDA Suite
Loading...
Searching...
No Matches
aui_json_serializer.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
21
23
24#include <wx/aui/framemanager.h>
25#if wxCHECK_VERSION( 3, 3, 0 )
26#include <wx/aui/serializer.h>
27#include <wx/aui/auibook.h>
28#endif
29#include <wx/log.h>
30#include <wx/window.h>
31
32#include <nlohmann/json.hpp>
33
34#include <algorithm>
35#include <limits>
36#include <set>
37#include <stdexcept>
38#include <string>
39#include <vector>
40
41namespace
42{
43#if wxCHECK_VERSION( 3, 3, 0 )
44
45struct PANE_METADATA
46{
47 wxAuiPaneInfo* paneInfo;
48 wxString name;
49 wxString windowName;
50 wxString className;
51 wxString caption;
52 bool isToolbar;
53 bool isCenter;
54 bool isNotebook;
55 size_t index;
56};
57
58static wxString getWindowName( wxWindow* aWindow )
59{
60 if( !aWindow )
61 return wxString();
62
63 wxString name = aWindow->GetName();
64
65 if( name.IsEmpty() )
66 // Fallback: use label if available (GetWindowVariant returns enum, not a string)
67 name = aWindow->GetLabel();
68
69 return name;
70}
71
72static PANE_METADATA buildMetadata( wxAuiPaneInfo& aInfo, size_t aIndex )
73{
74 PANE_METADATA meta;
75
76 meta.paneInfo = &aInfo;
77 meta.name = aInfo.name;
78 meta.caption = aInfo.caption;
79 meta.isToolbar = aInfo.IsToolbar();
80 meta.isCenter = ( aInfo.dock_direction == wxAUI_DOCK_CENTER );
81 meta.isNotebook = aInfo.window && wxDynamicCast( aInfo.window, wxAuiNotebook );
82 meta.index = aIndex;
83
84 if( aInfo.window )
85 {
86 meta.windowName = getWindowName( aInfo.window );
87
88 if( aInfo.window->GetClassInfo() )
89 meta.className = aInfo.window->GetClassInfo()->GetClassName();
90 }
91
92 return meta;
93}
94
95static void addDockLayout( nlohmann::json& aNode, const wxAuiDockLayoutInfo& aLayout )
96{
97 nlohmann::json dock = nlohmann::json::object();
98
99 dock["direction"] = aLayout.dock_direction;
100 dock["layer"] = aLayout.dock_layer;
101 dock["row"] = aLayout.dock_row;
102 dock["position"] = aLayout.dock_pos;
103 dock["proportion"] = aLayout.dock_proportion;
104 dock["size"] = aLayout.dock_size;
105
106 aNode["dock"] = std::move( dock );
107}
108
109static void readDockLayout( const nlohmann::json& aNode, wxAuiDockLayoutInfo& aLayout )
110{
111 if( !aNode.is_object() )
112 return;
113
114 aLayout.dock_direction = aNode.value( "direction", aLayout.dock_direction );
115 aLayout.dock_layer = aNode.value( "layer", aLayout.dock_layer );
116 aLayout.dock_row = aNode.value( "row", aLayout.dock_row );
117 aLayout.dock_pos = aNode.value( "position", aLayout.dock_pos );
118 aLayout.dock_proportion = aNode.value( "proportion", aLayout.dock_proportion );
119 aLayout.dock_size = aNode.value( "size", aLayout.dock_size );
120}
121
122class JSON_SERIALIZER : public wxAuiSerializer
123{
124public:
125 explicit JSON_SERIALIZER( wxAuiManager& aManager ) : m_manager( aManager ), m_paneIndex( 0 )
126 {
127 m_root = nlohmann::json::object();
128 }
129
130 nlohmann::json GetState() const
131 {
132 return m_root;
133 }
134
135 void BeforeSave() override
136 {
137 m_root["format"] = "kicad.wxaui";
138 m_root["version"] = 1;
139 }
140
141 void BeforeSavePanes() override
142 {
143 m_panes = nlohmann::json::array();
144 m_paneIndex = 0;
145 }
146
147 void SavePane( const wxAuiPaneLayoutInfo& aPane ) override
148 {
149 nlohmann::json pane = nlohmann::json::object();
150
151 pane["name"] = aPane.name.ToStdString();
152
153 addDockLayout( pane, aPane );
154
155 if( aPane.floating_pos != wxDefaultPosition || aPane.floating_size != wxDefaultSize )
156 {
157 nlohmann::json floating = nlohmann::json::object();
158 floating["rect"] = wxRect( aPane.floating_pos, aPane.floating_size );
159
160 pane["floating"] = std::move( floating );
161 }
162
163 if( aPane.is_maximized )
164 pane["maximized"] = true;
165
166 if( aPane.is_hidden )
167 pane["hidden"] = true;
168
169 wxAuiPaneInfo& info = m_manager.GetPane( aPane.name );
170
171 nlohmann::json meta = nlohmann::json::object();
172 meta["toolbar"] = info.IsToolbar();
173 meta["center"] = ( info.dock_direction == wxAUI_DOCK_CENTER );
174 meta["notebook"] = info.window && wxDynamicCast( info.window, wxAuiNotebook );
175 meta["index"] = m_paneIndex++;
176
177 if( info.window )
178 {
179 wxString windowName = getWindowName( info.window );
180
181 if( !windowName.IsEmpty() )
182 meta["window_name"] = windowName.ToStdString();
183
184 if( info.window->GetClassInfo() )
185 meta["class_name"] = wxString( info.window->GetClassInfo()->GetClassName() ).ToStdString();
186 }
187
188 if( !info.caption.IsEmpty() )
189 meta["caption"] = info.caption.ToStdString();
190
191 pane["meta"] = std::move( meta );
192
193 m_panes.push_back( std::move( pane ) );
194 }
195
196 void AfterSavePanes() override
197 {
198 m_root["panes"] = std::move( m_panes );
199 }
200
201 void BeforeSaveNotebooks() override
202 {
203 m_notebooks = nlohmann::json::array();
204 }
205
206 void BeforeSaveNotebook( const wxString& aName ) override
207 {
208 m_currentNotebook = nlohmann::json::object();
209 m_currentNotebook["name"] = aName.ToStdString();
210
211 wxAuiPaneInfo& info = m_manager.GetPane( aName );
212 nlohmann::json meta = nlohmann::json::object();
213
214 if( info.window )
215 {
216 wxString windowName = getWindowName( info.window );
217
218 if( !windowName.IsEmpty() )
219 meta["window_name"] = windowName.ToStdString();
220
221 if( info.window->GetClassInfo() )
222 meta["class_name"] = wxString( info.window->GetClassInfo()->GetClassName() ).ToStdString();
223 }
224
225 if( !info.caption.IsEmpty() )
226 meta["caption"] = info.caption.ToStdString();
227
228 meta["toolbar"] = info.IsToolbar();
229 meta["center"] = ( info.dock_direction == wxAUI_DOCK_CENTER );
230 meta["notebook"] = true;
231
232 m_currentNotebook["meta"] = std::move( meta );
233 m_currentNotebook["tabs"] = nlohmann::json::array();
234 }
235
236 void SaveNotebookTabControl( const wxAuiTabLayoutInfo& aTab ) override
237 {
238 nlohmann::json tab = nlohmann::json::object();
239
240 addDockLayout( tab, aTab );
241
242 if( !aTab.pages.empty() )
243 tab["pages"] = aTab.pages;
244
245 if( !aTab.pinned.empty() )
246 tab["pinned"] = aTab.pinned;
247
248 if( aTab.active >= 0 )
249 tab["active"] = aTab.active;
250
251 m_currentNotebook["tabs"].push_back( std::move( tab ) );
252 }
253
254 void AfterSaveNotebook() override
255 {
256 m_notebooks.push_back( std::move( m_currentNotebook ) );
257 m_currentNotebook = nlohmann::json();
258 }
259
260 void AfterSaveNotebooks() override
261 {
262 if( !m_notebooks.empty() )
263 m_root["notebooks"] = std::move( m_notebooks );
264 }
265
266 void AfterSave() override {}
267
268private:
269 wxAuiManager& m_manager;
270 nlohmann::json m_root;
271 nlohmann::json m_panes;
272 nlohmann::json m_notebooks;
273 nlohmann::json m_currentNotebook;
274 size_t m_paneIndex;
275};
276
277class JSON_DESERIALIZER : public wxAuiDeserializer
278{
279public:
280 JSON_DESERIALIZER( wxAuiManager& aManager, const nlohmann::json& aState )
281 : wxAuiDeserializer( aManager ), m_manager( aManager ), m_state( aState )
282 {
283 if( !m_state.is_object() )
284 throw std::runtime_error( "Invalid AUI layout state" );
285
286 const std::string format = m_state.value( "format", std::string() );
287
288 if( format != "kicad.wxaui" )
289 throw std::runtime_error( "Unsupported AUI layout format" );
290
291 int version = m_state.value( "version", 0 );
292
293 if( version != 1 )
294 throw std::runtime_error( "Unsupported AUI layout version" );
295
296 if( m_state.contains( "panes" ) && m_state["panes"].is_array() )
297 m_serializedPanes = m_state["panes"].get<std::vector<nlohmann::json>>();
298
299 if( m_state.contains( "notebooks" ) && m_state["notebooks"].is_array() )
300 m_serializedNotebooks = m_state["notebooks"].get<std::vector<nlohmann::json>>();
301 }
302
303 std::vector<wxAuiPaneLayoutInfo> LoadPanes() override
304 {
305 std::vector<wxAuiPaneLayoutInfo> panes;
306
307 wxAuiPaneInfoArray paneArray = m_manager.GetAllPanes();
308
309 std::vector<PANE_METADATA> metadata;
310 metadata.reserve( paneArray.GetCount() );
311
312 for( size_t i = 0; i < paneArray.GetCount(); ++i )
313 metadata.push_back( buildMetadata( paneArray[i], i ) );
314
315 std::set<wxAuiPaneInfo*> used;
316
317 for( const nlohmann::json& jsonPane : m_serializedPanes )
318 {
319 if( !jsonPane.is_object() )
320 continue;
321
322 wxAuiPaneLayoutInfo pane( wxString::FromUTF8( jsonPane.value( "name", std::string() ) ) );
323
324 readDockLayout( jsonPane.value( "dock", nlohmann::json::object() ), pane );
325
326 if( jsonPane.contains( "floating" ) )
327 {
328 const nlohmann::json& floating = jsonPane["floating"];
329
330 if( floating.contains( "rect" ) )
331 {
332 wxRect rect = floating["rect"].get<wxRect>();
333 pane.floating_pos = rect.GetPosition();
334 pane.floating_size = rect.GetSize();
335 }
336 }
337
338 pane.is_maximized = jsonPane.value( "maximized", false );
339 pane.is_hidden = jsonPane.value( "hidden", false );
340
341 wxAuiPaneInfo* actualPane = matchPane( jsonPane, metadata, used );
342
343 if( actualPane )
344 {
345 // Fixed docks size to content that follows DPI, a saved physical size clips them
346 if( actualPane->HasFlag( wxAuiPaneInfo::optionDockFixed ) )
347 pane.dock_size = 0;
348
349 pane.name = actualPane->name;
350 used.insert( actualPane );
351 panes.push_back( pane );
352 }
353 }
354
355 return panes;
356 }
357
358 std::vector<wxAuiTabLayoutInfo> LoadNotebookTabs( const wxString& aName ) override
359 {
360 const wxAuiPaneInfo& paneInfo = m_manager.GetPane( aName );
361
362 auto loadTabs = []( const nlohmann::json& aNotebook )
363 {
364 std::vector<wxAuiTabLayoutInfo> tabs;
365
366 if( !aNotebook.contains( "tabs" ) || !aNotebook["tabs"].is_array() )
367 return tabs;
368
369 for( const nlohmann::json& tabJson : aNotebook["tabs"].get<std::vector<nlohmann::json>>() )
370 {
371 if( !tabJson.is_object() )
372 continue;
373
374 wxAuiTabLayoutInfo info;
375
376 readDockLayout( tabJson.value( "dock", nlohmann::json::object() ), info );
377
378 if( tabJson.contains( "pages" ) )
379 {
380 info.pages.clear();
381 for( int page : tabJson["pages"].get<std::vector<int>>() )
382 info.pages.push_back( page );
383 }
384
385 if( tabJson.contains( "pinned" ) )
386 {
387 info.pinned.clear();
388 for( int page : tabJson["pinned"].get<std::vector<int>>() )
389 info.pinned.push_back( page );
390 }
391
392 info.active = tabJson.value( "active", info.active );
393
394 tabs.push_back( info );
395 }
396
397 return tabs;
398 };
399
400 for( const nlohmann::json& notebook : m_serializedNotebooks )
401 {
402 if( !notebook.is_object() )
403 continue;
404
405 wxString storedName = wxString::FromUTF8( notebook.value( "name", std::string() ) );
406
407 if( storedName == aName )
408 return loadTabs( notebook );
409 }
410
411 if( !paneInfo.IsOk() )
412 return {};
413
414 wxString windowName = paneInfo.window ? getWindowName( paneInfo.window ) : wxString();
415 wxString className;
416
417 if( paneInfo.window && paneInfo.window->GetClassInfo() )
418 className = paneInfo.window->GetClassInfo()->GetClassName();
419
420 for( const nlohmann::json& notebook : m_serializedNotebooks )
421 {
422 if( !notebook.is_object() )
423 continue;
424
425 const nlohmann::json& meta = notebook.value( "meta", nlohmann::json::object() );
426
427 const wxString storedWindowName = wxString::FromUTF8( meta.value( "window_name", std::string() ) );
428 const wxString storedClassName = wxString::FromUTF8( meta.value( "class_name", std::string() ) );
429
430 const bool toolbar = meta.value( "toolbar", false );
431 const bool center = meta.value( "center", false );
432 const bool notebookFlag = meta.value( "notebook", false );
433
434 if( toolbar != paneInfo.IsToolbar() || center != ( paneInfo.dock_direction == wxAUI_DOCK_CENTER ) )
435 continue;
436
437 if( !notebookFlag )
438 continue;
439
440 if( !windowName.IsEmpty() && !storedWindowName.IsEmpty() && windowName == storedWindowName )
441 return loadTabs( notebook );
442
443 if( !className.IsEmpty() && !storedClassName.IsEmpty() && className == storedClassName )
444 return loadTabs( notebook );
445 }
446
447 return {};
448 }
449
450 wxWindow* CreatePaneWindow( wxAuiPaneInfo& ) override
451 {
452 return nullptr;
453 }
454
455private:
456 wxAuiPaneInfo* matchPane( const nlohmann::json& aJson,
457 const std::vector<PANE_METADATA>& aMetadata,
458 const std::set<wxAuiPaneInfo*>& aUsed ) const
459 {
460 auto selectCandidate = [&aUsed]( wxAuiPaneInfo* pane ) -> bool
461 {
462 return pane && aUsed.find( pane ) == aUsed.end();
463 };
464
465 wxString storedName = wxString::FromUTF8( aJson.value( "name", std::string() ) );
466
467 if( !storedName.IsEmpty() )
468 {
469 wxAuiPaneInfo& pane = m_manager.GetPane( storedName );
470
471 if( pane.IsOk() && selectCandidate( &pane ) )
472 return &pane;
473 }
474
475 wxAuiPaneInfo* match = nullptr;
476 const nlohmann::json& meta = aJson.value( "meta", nlohmann::json::object() );
477
478 const wxString windowName = wxString::FromUTF8( meta.value( "window_name", std::string() ) );
479 const wxString className = wxString::FromUTF8( meta.value( "class_name", std::string() ) );
480 const wxString caption = wxString::FromUTF8( meta.value( "caption", std::string() ) );
481 const bool isToolbar = meta.value( "toolbar", false );
482 const bool isCenter = meta.value( "center", false );
483 const bool isNotebook = meta.value( "notebook", false );
484 const size_t index = meta.value( "index", std::numeric_limits<size_t>::max() );
485
486 auto findBy = [&]( auto predicate ) -> wxAuiPaneInfo*
487 {
488 wxAuiPaneInfo* candidate = nullptr;
489
490 for( const PANE_METADATA& data : aMetadata )
491 {
492 if( !predicate( data ) )
493 continue;
494
495 if( !selectCandidate( data.paneInfo ) )
496 continue;
497
498 if( candidate )
499 return nullptr;
500
501 candidate = data.paneInfo;
502 }
503
504 return candidate;
505 };
506
507 if( !windowName.IsEmpty() )
508 {
509 match = findBy( [&]( const PANE_METADATA& data )
510 {
511 return data.windowName == windowName;
512 } );
513
514 if( match )
515 return match;
516 }
517
518 if( !className.IsEmpty() )
519 {
520 match = findBy( [&]( const PANE_METADATA& data )
521 {
522 if( data.className != className )
523 return false;
524
525 if( data.isToolbar != isToolbar )
526 return false;
527
528 if( data.isCenter != isCenter )
529 return false;
530
531 if( data.isNotebook != isNotebook )
532 return false;
533
534 return true;
535 } );
536
537 if( match )
538 return match;
539 }
540
541 if( index != std::numeric_limits<size_t>::max() )
542 {
543 match = findBy( [&]( const PANE_METADATA& data )
544 {
545 return data.index == index;
546 } );
547
548 if( match )
549 return match;
550 }
551
552 if( !caption.IsEmpty() )
553 {
554 match = findBy( [&]( const PANE_METADATA& data )
555 {
556 return data.caption == caption;
557 } );
558
559 if( match )
560 return match;
561 }
562
563 match = findBy( [&]( const PANE_METADATA& data )
564 {
565 if( data.isToolbar != isToolbar )
566 return false;
567
568 if( data.isNotebook != isNotebook )
569 return false;
570
571 return true;
572 } );
573
574 return match;
575 }
576
577 wxAuiManager& m_manager;
578 nlohmann::json m_state;
579 std::vector<nlohmann::json> m_serializedPanes;
580 std::vector<nlohmann::json> m_serializedNotebooks;
581};
582
583#endif
584} // namespace
585
586WX_AUI_JSON_SERIALIZER::WX_AUI_JSON_SERIALIZER( wxAuiManager& aManager ) : m_manager( aManager )
587{
588}
589
591{
592#if wxCHECK_VERSION( 3, 3, 0 )
593 try
594 {
595 JSON_SERIALIZER serializer( m_manager );
596 m_manager.SaveLayout( serializer );
597 return serializer.GetState();
598 }
599 catch( const std::exception& err )
600 {
601 wxLogWarning( "Failed to serialize AUI layout: %s", err.what() );
602 }
603#endif
604
605 return nlohmann::json();
606}
607
608bool WX_AUI_JSON_SERIALIZER::Deserialize( const nlohmann::json& aState ) const
609{
610#if wxCHECK_VERSION( 3, 3, 0 )
611 if( aState.is_null() || aState.empty() )
612 return false;
613
614 try
615 {
616 JSON_DESERIALIZER deserializer( m_manager, aState );
617 m_manager.LoadLayout( deserializer );
618 return true;
619 }
620 catch( const std::exception& err )
621 {
622 wxLogWarning( "Failed to deserialize AUI layout: %s", err.what() );
623 }
624#endif
625
626 return false;
627}
int index
const char * name
bool Deserialize(const nlohmann::json &aState) const
nlohmann::json Serialize() const
WX_AUI_JSON_SERIALIZER(wxAuiManager &aManager)