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 along
17 * with this program. If not, see <http://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 pane.name = actualPane->name;
346 used.insert( actualPane );
347 panes.push_back( pane );
348 }
349 }
350
351 return panes;
352 }
353
354 std::vector<wxAuiTabLayoutInfo> LoadNotebookTabs( const wxString& aName ) override
355 {
356 const wxAuiPaneInfo& paneInfo = m_manager.GetPane( aName );
357
358 auto loadTabs = []( const nlohmann::json& aNotebook )
359 {
360 std::vector<wxAuiTabLayoutInfo> tabs;
361
362 if( !aNotebook.contains( "tabs" ) || !aNotebook["tabs"].is_array() )
363 return tabs;
364
365 for( const nlohmann::json& tabJson : aNotebook["tabs"].get<std::vector<nlohmann::json>>() )
366 {
367 if( !tabJson.is_object() )
368 continue;
369
370 wxAuiTabLayoutInfo info;
371
372 readDockLayout( tabJson.value( "dock", nlohmann::json::object() ), info );
373
374 if( tabJson.contains( "pages" ) )
375 {
376 info.pages.clear();
377 for( int page : tabJson["pages"].get<std::vector<int>>() )
378 info.pages.push_back( page );
379 }
380
381 if( tabJson.contains( "pinned" ) )
382 {
383 info.pinned.clear();
384 for( int page : tabJson["pinned"].get<std::vector<int>>() )
385 info.pinned.push_back( page );
386 }
387
388 info.active = tabJson.value( "active", info.active );
389
390 tabs.push_back( info );
391 }
392
393 return tabs;
394 };
395
396 for( const nlohmann::json& notebook : m_serializedNotebooks )
397 {
398 if( !notebook.is_object() )
399 continue;
400
401 wxString storedName = wxString::FromUTF8( notebook.value( "name", std::string() ) );
402
403 if( storedName == aName )
404 return loadTabs( notebook );
405 }
406
407 if( !paneInfo.IsOk() )
408 return {};
409
410 wxString windowName = paneInfo.window ? getWindowName( paneInfo.window ) : wxString();
411 wxString className;
412
413 if( paneInfo.window && paneInfo.window->GetClassInfo() )
414 className = paneInfo.window->GetClassInfo()->GetClassName();
415
416 for( const nlohmann::json& notebook : m_serializedNotebooks )
417 {
418 if( !notebook.is_object() )
419 continue;
420
421 const nlohmann::json& meta = notebook.value( "meta", nlohmann::json::object() );
422
423 const wxString storedWindowName = wxString::FromUTF8( meta.value( "window_name", std::string() ) );
424 const wxString storedClassName = wxString::FromUTF8( meta.value( "class_name", std::string() ) );
425
426 const bool toolbar = meta.value( "toolbar", false );
427 const bool center = meta.value( "center", false );
428 const bool notebookFlag = meta.value( "notebook", false );
429
430 if( toolbar != paneInfo.IsToolbar() || center != ( paneInfo.dock_direction == wxAUI_DOCK_CENTER ) )
431 continue;
432
433 if( !notebookFlag )
434 continue;
435
436 if( !windowName.IsEmpty() && !storedWindowName.IsEmpty() && windowName == storedWindowName )
437 return loadTabs( notebook );
438
439 if( !className.IsEmpty() && !storedClassName.IsEmpty() && className == storedClassName )
440 return loadTabs( notebook );
441 }
442
443 return {};
444 }
445
446 wxWindow* CreatePaneWindow( wxAuiPaneInfo& ) override
447 {
448 return nullptr;
449 }
450
451private:
452 wxAuiPaneInfo* matchPane( const nlohmann::json& aJson,
453 const std::vector<PANE_METADATA>& aMetadata,
454 const std::set<wxAuiPaneInfo*>& aUsed ) const
455 {
456 auto selectCandidate = [&aUsed]( wxAuiPaneInfo* pane ) -> bool
457 {
458 return pane && aUsed.find( pane ) == aUsed.end();
459 };
460
461 wxString storedName = wxString::FromUTF8( aJson.value( "name", std::string() ) );
462
463 if( !storedName.IsEmpty() )
464 {
465 wxAuiPaneInfo& pane = m_manager.GetPane( storedName );
466
467 if( pane.IsOk() && selectCandidate( &pane ) )
468 return &pane;
469 }
470
471 wxAuiPaneInfo* match = nullptr;
472 const nlohmann::json& meta = aJson.value( "meta", nlohmann::json::object() );
473
474 const wxString windowName = wxString::FromUTF8( meta.value( "window_name", std::string() ) );
475 const wxString className = wxString::FromUTF8( meta.value( "class_name", std::string() ) );
476 const wxString caption = wxString::FromUTF8( meta.value( "caption", std::string() ) );
477 const bool isToolbar = meta.value( "toolbar", false );
478 const bool isCenter = meta.value( "center", false );
479 const bool isNotebook = meta.value( "notebook", false );
480 const size_t index = meta.value( "index", std::numeric_limits<size_t>::max() );
481
482 auto findBy = [&]( auto predicate ) -> wxAuiPaneInfo*
483 {
484 wxAuiPaneInfo* candidate = nullptr;
485
486 for( const PANE_METADATA& data : aMetadata )
487 {
488 if( !predicate( data ) )
489 continue;
490
491 if( !selectCandidate( data.paneInfo ) )
492 continue;
493
494 if( candidate )
495 return nullptr;
496
497 candidate = data.paneInfo;
498 }
499
500 return candidate;
501 };
502
503 if( !windowName.IsEmpty() )
504 {
505 match = findBy( [&]( const PANE_METADATA& data )
506 {
507 return data.windowName == windowName;
508 } );
509
510 if( match )
511 return match;
512 }
513
514 if( !className.IsEmpty() )
515 {
516 match = findBy( [&]( const PANE_METADATA& data )
517 {
518 if( data.className != className )
519 return false;
520
521 if( data.isToolbar != isToolbar )
522 return false;
523
524 if( data.isCenter != isCenter )
525 return false;
526
527 if( data.isNotebook != isNotebook )
528 return false;
529
530 return true;
531 } );
532
533 if( match )
534 return match;
535 }
536
537 if( index != std::numeric_limits<size_t>::max() )
538 {
539 match = findBy( [&]( const PANE_METADATA& data )
540 {
541 return data.index == index;
542 } );
543
544 if( match )
545 return match;
546 }
547
548 if( !caption.IsEmpty() )
549 {
550 match = findBy( [&]( const PANE_METADATA& data )
551 {
552 return data.caption == caption;
553 } );
554
555 if( match )
556 return match;
557 }
558
559 match = findBy( [&]( const PANE_METADATA& data )
560 {
561 if( data.isToolbar != isToolbar )
562 return false;
563
564 if( data.isNotebook != isNotebook )
565 return false;
566
567 return true;
568 } );
569
570 return match;
571 }
572
573 wxAuiManager& m_manager;
574 nlohmann::json m_state;
575 std::vector<nlohmann::json> m_serializedPanes;
576 std::vector<nlohmann::json> m_serializedNotebooks;
577};
578
579#endif
580} // namespace
581
582WX_AUI_JSON_SERIALIZER::WX_AUI_JSON_SERIALIZER( wxAuiManager& aManager ) : m_manager( aManager )
583{
584}
585
587{
588#if wxCHECK_VERSION( 3, 3, 0 )
589 try
590 {
591 JSON_SERIALIZER serializer( m_manager );
592 m_manager.SaveLayout( serializer );
593 return serializer.GetState();
594 }
595 catch( const std::exception& err )
596 {
597 wxLogWarning( "Failed to serialize AUI layout: %s", err.what() );
598 }
599#endif
600
601 return nlohmann::json();
602}
603
604bool WX_AUI_JSON_SERIALIZER::Deserialize( const nlohmann::json& aState ) const
605{
606#if wxCHECK_VERSION( 3, 3, 0 )
607 if( aState.is_null() || aState.empty() )
608 return false;
609
610 try
611 {
612 JSON_DESERIALIZER deserializer( m_manager, aState );
613 m_manager.LoadLayout( deserializer );
614 return true;
615 }
616 catch( const std::exception& err )
617 {
618 wxLogWarning( "Failed to deserialize AUI layout: %s", err.what() );
619 }
620#endif
621
622 return false;
623}
const char * name
bool Deserialize(const nlohmann::json &aState) const
nlohmann::json Serialize() const
WX_AUI_JSON_SERIALIZER(wxAuiManager &aManager)