1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2020 Jon Evans <jon@craftyjon.com>
5  * Copyright (C) 2020-2021 KiCad Developers, see AUTHORS.txt for contributors.
6  *
7  * This program is free software: you can redistribute it and/or modify it
8  * under the terms of the GNU General Public License as published by the
9  * Free Software Foundation, either version 3 of the License, or (at your
10  * option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License along
18  * with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #include <algorithm>
22 #include <fstream>
23 #include <iomanip>
24 #include <utility>
25 #include <sstream>
26 
27 #include <locale_io.h>
28 #include <gal/color4d.h>
29 #include <settings/json_settings.h>
30 #include <settings/json_settings_internals.h>
31 #include <settings/nested_settings.h>
32 #include <settings/parameters.h>
33 #include <wx/config.h>
34 #include <wx/debug.h>
35 #include <wx/fileconf.h>
36 #include <wx/filename.h>
37 #include <wx/log.h>
38 #include <wx/stdstream.h>
39 #include <wx/wfstream.h>
40 
41 const wxChar* const traceSettings = wxT( "KICAD_SETTINGS" );
42 
43 
PointerFromString(std::string aPath)44 nlohmann::json::json_pointer JSON_SETTINGS_INTERNALS::PointerFromString( std::string aPath )
45 {
46     std::replace( aPath.begin(), aPath.end(), '.', '/' );
47     aPath.insert( 0, "/" );
48 
49     nlohmann::json::json_pointer p;
50 
51     try
52     {
53         p = nlohmann::json::json_pointer( aPath );
54     }
55     catch( ... )
56     {
57         wxASSERT_MSG( false, wxT( "Invalid pointer path in PointerFromString!" ) );
58     }
59 
60     return p;
61 }
62 
63 
JSON_SETTINGS(const wxString & aFilename,SETTINGS_LOC aLocation,int aSchemaVersion,bool aCreateIfMissing,bool aCreateIfDefault,bool aWriteFile)64 JSON_SETTINGS::JSON_SETTINGS( const wxString& aFilename, SETTINGS_LOC aLocation,
65                               int aSchemaVersion, bool aCreateIfMissing, bool aCreateIfDefault,
66                               bool aWriteFile ) :
67         m_filename( aFilename ),
68         m_legacy_filename( "" ),
69         m_location( aLocation ),
70         m_createIfMissing( aCreateIfMissing ),
71         m_createIfDefault( aCreateIfDefault ),
72         m_writeFile( aWriteFile ),
73         m_deleteLegacyAfterMigration( true ),
74         m_resetParamsIfMissing( true ),
75         m_schemaVersion( aSchemaVersion ),
76         m_manager( nullptr )
77 {
78     m_internals = std::make_unique<JSON_SETTINGS_INTERNALS>();
79 
80     try
81     {
82         m_internals->SetFromString( "meta.filename", GetFullFilename() );
83     }
84     catch( ... )
85     {
86         wxLogTrace( traceSettings, "Error: Could not create filename field for %s",
87                     GetFullFilename() );
88     }
89 
90 
91     m_params.emplace_back(
92             new PARAM<int>( "meta.version", &m_schemaVersion, m_schemaVersion, true ) );
93 }
94 
95 
~JSON_SETTINGS()96 JSON_SETTINGS::~JSON_SETTINGS()
97 {
98     for( auto param: m_params )
99         delete param;
100 
101     m_params.clear();
102 }
103 
104 
GetFullFilename() const105 wxString JSON_SETTINGS::GetFullFilename() const
106 {
107     return wxString( m_filename + "." + getFileExt() );
108 }
109 
110 
At(const std::string & aPath)111 nlohmann::json& JSON_SETTINGS::At( const std::string& aPath )
112 {
113     return m_internals->At( aPath );
114 }
115 
116 
Contains(const std::string & aPath) const117 bool JSON_SETTINGS::Contains( const std::string& aPath ) const
118 {
119     return m_internals->contains( JSON_SETTINGS_INTERNALS::PointerFromString( aPath ) );
120 }
121 
122 
Count(const std::string & aPath) const123 size_t JSON_SETTINGS::Count( const std::string& aPath ) const
124 {
125     return m_internals->count( JSON_SETTINGS_INTERNALS::PointerFromString( aPath ) );
126 }
127 
128 
Internals()129 JSON_SETTINGS_INTERNALS* JSON_SETTINGS::Internals()
130 {
131     return m_internals.get();
132 }
133 
134 
Load()135 void JSON_SETTINGS::Load()
136 {
137     for( auto param : m_params )
138     {
139         try
140         {
141             param->Load( this, m_resetParamsIfMissing );
142         }
143         catch( ... )
144         {
145             // Skip unreadable parameters in file
146             wxLogTrace( traceSettings, "param '%s' load err", param->GetJsonPath().c_str() );
147         }
148     }
149 }
150 
151 
LoadFromFile(const wxString & aDirectory)152 bool JSON_SETTINGS::LoadFromFile( const wxString& aDirectory )
153 {
154     // First, load all params to default values
155     m_internals->clear();
156     Load();
157 
158     bool success         = true;
159     bool migrated        = false;
160     bool legacy_migrated = false;
161 
162     LOCALE_IO locale;
163 
164     auto migrateFromLegacy = [&] ( wxFileName& aPath ) {
165         // Backup and restore during migration so that the original can be mutated if convenient
166         bool backed_up = false;
167         wxFileName temp;
168 
169         if( aPath.IsDirWritable() )
170         {
171             temp.AssignTempFileName( aPath.GetFullPath() );
172 
173             if( !wxCopyFile( aPath.GetFullPath(), temp.GetFullPath() ) )
174             {
175                 wxLogTrace( traceSettings, "%s: could not create temp file for migration",
176                         GetFullFilename() );
177             }
178             else
179                 backed_up = true;
180         }
181 
182         // Silence popups if legacy file is read-only
183         wxLogNull doNotLog;
184 
185         wxConfigBase::DontCreateOnDemand();
186         auto cfg = std::make_unique<wxFileConfig>( wxT( "" ), wxT( "" ), aPath.GetFullPath() );
187 
188         // If migrate fails or is not implemented, fall back to built-in defaults that were
189         // already loaded above
190         if( !MigrateFromLegacy( cfg.get() ) )
191         {
192             wxLogTrace( traceSettings,
193                         "%s: migrated; not all settings were found in legacy file",
194                         GetFullFilename() );
195         }
196         else
197         {
198             wxLogTrace( traceSettings, "%s: migrated from legacy format", GetFullFilename() );
199         }
200 
201         if( backed_up )
202         {
203             cfg.reset();
204 
205             if( !wxCopyFile( temp.GetFullPath(), aPath.GetFullPath() ) )
206             {
207                 wxLogTrace( traceSettings,
208                             "migrate; copy temp file %s to %s failed",
209                             temp.GetFullPath(), aPath.GetFullPath() );
210             }
211 
212             if( !wxRemoveFile( temp.GetFullPath() ) )
213             {
214                 wxLogTrace( traceSettings,
215                             "migrate; failed to remove temp file %s",
216                             temp.GetFullPath() );
217             }
218          }
219 
220         // Either way, we want to clean up the old file afterwards
221         legacy_migrated = true;
222     };
223 
224     wxFileName path;
225 
226     if( aDirectory.empty() )
227     {
228         path.Assign( m_filename );
229         path.SetExt( getFileExt() );
230     }
231     else
232     {
233         wxString dir( aDirectory );
234         path.Assign( dir, m_filename, getFileExt() );
235     }
236 
237     if( !path.Exists() )
238     {
239         // Case 1: legacy migration, no .json extension yet
240         path.SetExt( getLegacyFileExt() );
241 
242         if( path.Exists() )
243         {
244             migrateFromLegacy( path );
245         }
246         // Case 2: legacy filename is different from new one
247         else if( !m_legacy_filename.empty() )
248         {
249             path.SetName( m_legacy_filename );
250 
251             if( path.Exists() )
252                 migrateFromLegacy( path );
253         }
254         else
255         {
256             success = false;
257         }
258     }
259     else
260     {
261         if( !path.IsFileWritable() )
262             m_writeFile = false;
263 
264         try
265         {
266             wxFFileInputStream fp( path.GetFullPath(), wxT( "rt" ) );
267             wxStdInputStream fstream( fp );
268 
269             if( fp.IsOk() )
270             {
271                 *static_cast<nlohmann::json*>( m_internals.get() ) =
272                         nlohmann::json::parse( fstream, nullptr,
273                                                /* allow_exceptions = */ true,
274                                                /* ignore_comments  = */ true );
275 
276                 // If parse succeeds, check if schema migration is required
277                 int filever = -1;
278 
279                 try
280                 {
281                     filever = m_internals->Get<int>( "meta.version" );
282                 }
283                 catch( ... )
284                 {
285                     wxLogTrace( traceSettings, "%s: file version could not be read!",
286                                 GetFullFilename() );
287                     success = false;
288                 }
289 
290                 if( filever >= 0 && filever < m_schemaVersion )
291                 {
292                     wxLogTrace( traceSettings, "%s: attempting migration from version %d to %d",
293                                 GetFullFilename(), filever, m_schemaVersion );
294 
295                     if( Migrate() )
296                     {
297                         migrated = true;
298                     }
299                     else
300                     {
301                         wxLogTrace( traceSettings, "%s: migration failed!", GetFullFilename() );
302                     }
303                 }
304                 else if( filever > m_schemaVersion )
305                 {
306                     wxLogTrace( traceSettings,
307                                 "%s: warning: file version %d is newer than latest (%d)",
308                                 GetFullFilename(), filever, m_schemaVersion );
309                 }
310             }
311             else
312             {
313                 wxLogTrace( traceSettings, "%s exists but can't be opened for read",
314                             GetFullFilename() );
315             }
316         }
317         catch( nlohmann::json::parse_error& error )
318         {
319             wxLogTrace( traceSettings, "Json parse error reading %s: %s",
320                         path.GetFullPath(), error.what() );
321             wxLogTrace( traceSettings, "Attempting migration in case file is in legacy format" );
322             migrateFromLegacy( path );
323         }
324     }
325 
326     // Now that we have new data in the JSON structure, load the params again
327     Load();
328 
329     // And finally load any nested settings
330     for( auto settings : m_nested_settings )
331         settings->LoadFromFile();
332 
333     wxLogTrace( traceSettings, "Loaded <%s> with schema %d", GetFullFilename(), m_schemaVersion );
334 
335     // If we migrated, clean up the legacy file (with no extension)
336     if( legacy_migrated || migrated )
337     {
338         if( legacy_migrated && m_deleteLegacyAfterMigration && !wxRemoveFile( path.GetFullPath() ) )
339         {
340             wxLogTrace( traceSettings, "Warning: could not remove legacy file %s",
341                         path.GetFullPath() );
342         }
343 
344         // And write-out immediately so that we don't lose data if the program later crashes.
345         if( m_deleteLegacyAfterMigration )
346             SaveToFile( aDirectory, true );
347     }
348 
349     return success;
350 }
351 
352 
Store()353 bool JSON_SETTINGS::Store()
354 {
355     bool modified = false;
356 
357     for( auto param : m_params )
358     {
359         modified |= !param->MatchesFile( this );
360         param->Store( this );
361     }
362 
363     return modified;
364 }
365 
366 
ResetToDefaults()367 void JSON_SETTINGS::ResetToDefaults()
368 {
369     for( auto param : m_params )
370         param->SetDefault();
371 }
372 
373 
SaveToFile(const wxString & aDirectory,bool aForce)374 bool JSON_SETTINGS::SaveToFile( const wxString& aDirectory, bool aForce )
375 {
376     if( !m_writeFile )
377         return false;
378 
379     // Default PROJECT won't have a filename set
380     if( m_filename.IsEmpty() )
381         return false;
382 
383     wxFileName path;
384 
385     if( aDirectory.empty() )
386     {
387         path.Assign( m_filename );
388         path.SetExt( getFileExt() );
389     }
390     else
391     {
392         wxString dir( aDirectory );
393         path.Assign( dir, m_filename, getFileExt() );
394     }
395 
396     if( !m_createIfMissing && !path.FileExists() )
397     {
398         wxLogTrace( traceSettings,
399                 "File for %s doesn't exist and m_createIfMissing == false; not saving",
400                 GetFullFilename() );
401         return false;
402     }
403 
404     // Ensure the path exists, and create it if not.
405     if( !path.DirExists() && !path.Mkdir() )
406     {
407         wxLogTrace( traceSettings, "Warning: could not create path %s, can't save %s",
408                     path.GetPath(), GetFullFilename() );
409         return false;
410     }
411 
412     if( ( path.FileExists() && !path.IsFileWritable() ) ||
413         ( !path.FileExists() && !path.IsDirWritable() ) )
414     {
415         wxLogTrace( traceSettings, "File for %s is read-only; not saving", GetFullFilename() );
416         return false;
417     }
418 
419     bool modified = false;
420 
421     for( auto settings : m_nested_settings )
422         modified |= settings->SaveToFile();
423 
424     modified |= Store();
425 
426     if( !modified && !aForce && path.FileExists() )
427     {
428         wxLogTrace( traceSettings, "%s contents not modified, skipping save", GetFullFilename() );
429         return false;
430     }
431     else if( !modified && !aForce && !m_createIfDefault )
432     {
433         wxLogTrace( traceSettings,
434                 "%s contents still default and m_createIfDefault == false; not saving",
435                     GetFullFilename() );
436         return false;
437     }
438 
439     wxLogTrace( traceSettings, "Saving %s", GetFullFilename() );
440 
441     LOCALE_IO dummy;
442     bool success = true;
443 
444     try
445     {
446         std::stringstream buffer;
447         buffer << std::setw( 2 ) << *m_internals << std::endl;
448 
449         wxFFileOutputStream fileStream( path.GetFullPath(), "wb" );
450 
451         if( !fileStream.IsOk()
452                 || !fileStream.WriteAll( buffer.str().c_str(), buffer.str().size() ) )
453         {
454             wxLogTrace( traceSettings, "Warning: could not save %s", GetFullFilename() );
455             success = false;
456         }
457     }
458     catch( nlohmann::json::exception& error )
459     {
460         wxLogTrace( traceSettings, "Catch error: could not save %s. Json error %s",
461                     GetFullFilename(), error.what() );
462         success = false;
463     }
464     catch( ... )
465     {
466         wxLogTrace( traceSettings, "Error: could not save %s." );
467         success = false;
468     }
469 
470     return success;
471 }
472 
473 
GetJson(const std::string & aPath) const474 OPT<nlohmann::json> JSON_SETTINGS::GetJson( const std::string& aPath ) const
475 {
476     nlohmann::json::json_pointer ptr = m_internals->PointerFromString( aPath );
477 
478     if( m_internals->contains( ptr ) )
479     {
480         try
481         {
482             return OPT<nlohmann::json>{ m_internals->at( ptr ) };
483         }
484         catch( ... )
485         {
486         }
487     }
488 
489     return OPT<nlohmann::json>{};
490 }
491 
492 
493 template<typename ValueType>
Get(const std::string & aPath) const494 OPT<ValueType> JSON_SETTINGS::Get( const std::string& aPath ) const
495 {
496     if( OPT<nlohmann::json> ret = GetJson( aPath ) )
497     {
498         try
499         {
500             return ret->get<ValueType>();
501         }
502         catch( ... )
503         {
504         }
505     }
506 
507     return NULLOPT;
508 }
509 
510 
511 // Instantiate all required templates here to allow reducing scope of json.hpp
512 template OPT<bool> JSON_SETTINGS::Get<bool>( const std::string& aPath ) const;
513 template OPT<double> JSON_SETTINGS::Get<double>( const std::string& aPath ) const;
514 template OPT<float> JSON_SETTINGS::Get<float>( const std::string& aPath ) const;
515 template OPT<int> JSON_SETTINGS::Get<int>( const std::string& aPath ) const;
516 template OPT<unsigned int> JSON_SETTINGS::Get<unsigned int>( const std::string& aPath ) const;
517 template OPT<unsigned long long> JSON_SETTINGS::Get<unsigned long long>( const std::string& aPath ) const;
518 template OPT<std::string> JSON_SETTINGS::Get<std::string>( const std::string& aPath ) const;
519 template OPT<nlohmann::json> JSON_SETTINGS::Get<nlohmann::json>( const std::string& aPath ) const;
520 template OPT<KIGFX::COLOR4D> JSON_SETTINGS::Get<KIGFX::COLOR4D>( const std::string& aPath ) const;
521 
522 
523 template<typename ValueType>
Set(const std::string & aPath,ValueType aVal)524 void JSON_SETTINGS::Set( const std::string& aPath, ValueType aVal )
525 {
526     m_internals->SetFromString( aPath, aVal );
527 }
528 
529 
530 // Instantiate all required templates here to allow reducing scope of json.hpp
531 template void JSON_SETTINGS::Set<bool>( const std::string& aPath, bool aValue );
532 template void JSON_SETTINGS::Set<double>( const std::string& aPath, double aValue );
533 template void JSON_SETTINGS::Set<float>( const std::string& aPath, float aValue );
534 template void JSON_SETTINGS::Set<int>( const std::string& aPath, int aValue );
535 template void JSON_SETTINGS::Set<unsigned int>( const std::string& aPath, unsigned int aValue );
536 template void JSON_SETTINGS::Set<unsigned long long>( const std::string& aPath, unsigned long long aValue );
537 template void JSON_SETTINGS::Set<const char*>( const std::string& aPath, const char* aValue );
538 template void JSON_SETTINGS::Set<std::string>( const std::string& aPath, std::string aValue );
539 template void JSON_SETTINGS::Set<nlohmann::json>( const std::string& aPath, nlohmann::json aValue );
540 template void JSON_SETTINGS::Set<KIGFX::COLOR4D>( const std::string& aPath, KIGFX::COLOR4D aValue );
541 
542 
registerMigration(int aOldSchemaVersion,int aNewSchemaVersion,std::function<bool ()> aMigrator)543 void JSON_SETTINGS::registerMigration( int aOldSchemaVersion, int aNewSchemaVersion,
544                                        std::function<bool()> aMigrator )
545 {
546     wxASSERT( aNewSchemaVersion > aOldSchemaVersion );
547     wxASSERT( aNewSchemaVersion <= m_schemaVersion );
548     m_migrators[aOldSchemaVersion] = std::make_pair( aNewSchemaVersion, aMigrator );
549 }
550 
551 
Migrate()552 bool JSON_SETTINGS::Migrate()
553 {
554     int filever = m_internals->Get<int>( "meta.version" );
555 
556     while( filever < m_schemaVersion )
557     {
558         if( !m_migrators.count( filever ) )
559         {
560             wxLogTrace( traceSettings, "Migrator missing for %s version %d!",
561                         typeid( *this ).name(), filever );
562             return false;
563         }
564 
565         std::pair<int, std::function<bool()>> pair = m_migrators.at( filever );
566 
567         if( pair.second() )
568         {
569             wxLogTrace( traceSettings, "Migrated %s from %d to %d", typeid( *this ).name(),
570                         filever, pair.first );
571             filever = pair.first;
572             m_internals->At( "meta.version" ) = filever;
573         }
574         else
575         {
576             wxLogTrace( traceSettings, "Migration failed for %s from %d to %d",
577                         typeid( *this ).name(), filever, pair.first );
578             return false;
579         }
580     }
581 
582     return true;
583 }
584 
585 
MigrateFromLegacy(wxConfigBase * aLegacyConfig)586 bool JSON_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
587 {
588     wxLogTrace( traceSettings,
589             "MigrateFromLegacy() not implemented for %s", typeid( *this ).name() );
590     return false;
591 }
592 
593 
SetIfPresent(const nlohmann::json & aObj,const std::string & aPath,wxString & aTarget)594 bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
595                                   wxString& aTarget )
596 {
597     nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
598 
599     if( aObj.contains( ptr ) && aObj.at( ptr ).is_string() )
600     {
601         aTarget = aObj.at( ptr ).get<wxString>();
602         return true;
603     }
604 
605     return false;
606 }
607 
608 
SetIfPresent(const nlohmann::json & aObj,const std::string & aPath,bool & aTarget)609 bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
610                                   bool& aTarget )
611 {
612     nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
613 
614     if( aObj.contains( ptr ) && aObj.at( ptr ).is_boolean() )
615     {
616         aTarget = aObj.at( ptr ).get<bool>();
617         return true;
618     }
619 
620     return false;
621 }
622 
623 
SetIfPresent(const nlohmann::json & aObj,const std::string & aPath,int & aTarget)624 bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
625                                   int& aTarget )
626 {
627     nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
628 
629     if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_integer() )
630     {
631         aTarget = aObj.at( ptr ).get<int>();
632         return true;
633     }
634 
635     return false;
636 }
637 
638 
SetIfPresent(const nlohmann::json & aObj,const std::string & aPath,unsigned int & aTarget)639 bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
640                                   unsigned int& aTarget )
641 {
642     nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
643 
644     if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_unsigned() )
645     {
646         aTarget = aObj.at( ptr ).get<unsigned int>();
647         return true;
648     }
649 
650     return false;
651 }
652 
653 
654 template<typename ValueType>
fromLegacy(wxConfigBase * aConfig,const std::string & aKey,const std::string & aDest)655 bool JSON_SETTINGS::fromLegacy( wxConfigBase* aConfig, const std::string& aKey,
656                              const std::string& aDest )
657 {
658     ValueType val;
659 
660     if( aConfig->Read( aKey, &val ) )
661     {
662         try
663         {
664             ( *m_internals )[aDest] = val;
665         }
666         catch( ... )
667         {
668             wxASSERT_MSG( false, wxT( "Could not write value in fromLegacy!" ) );
669             return false;
670         }
671 
672         return true;
673     }
674 
675     return false;
676 }
677 
678 
679 // Explicitly declare these because we only support a few types anyway, and it means we can keep
680 // wxConfig detail out of the header file
681 template bool JSON_SETTINGS::fromLegacy<int>( wxConfigBase*, const std::string&,
682                                               const std::string& );
683 
684 template bool JSON_SETTINGS::fromLegacy<double>( wxConfigBase*, const std::string&,
685                                               const std::string& );
686 
687 template bool JSON_SETTINGS::fromLegacy<bool>( wxConfigBase*, const std::string&,
688                                                const std::string& );
689 
690 
fromLegacyString(wxConfigBase * aConfig,const std::string & aKey,const std::string & aDest)691 bool JSON_SETTINGS::fromLegacyString( wxConfigBase* aConfig, const std::string& aKey,
692                                       const std::string& aDest )
693 {
694     wxString str;
695 
696     if( aConfig->Read( aKey, &str ) )
697     {
698         try
699         {
700             ( *m_internals )[aDest] = str.ToUTF8();
701         }
702         catch( ... )
703         {
704             wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyString!" ) );
705             return false;
706         }
707 
708         return true;
709     }
710 
711     return false;
712 }
713 
714 
fromLegacyColor(wxConfigBase * aConfig,const std::string & aKey,const std::string & aDest)715 bool JSON_SETTINGS::fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
716     const std::string& aDest )
717 {
718     wxString str;
719 
720     if( aConfig->Read( aKey, &str ) )
721     {
722         KIGFX::COLOR4D color;
723         color.SetFromWxString( str );
724 
725         try
726         {
727             nlohmann::json js = nlohmann::json::array( { color.r, color.g, color.b, color.a } );
728             ( *m_internals )[aDest] = js;
729         }
730         catch( ... )
731         {
732             wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyColor!" ) );
733             return false;
734         }
735 
736         return true;
737     }
738 
739     return false;
740 }
741 
742 
AddNestedSettings(NESTED_SETTINGS * aSettings)743 void JSON_SETTINGS::AddNestedSettings( NESTED_SETTINGS* aSettings )
744 {
745     wxLogTrace( traceSettings, "AddNestedSettings %s", aSettings->GetFilename() );
746     m_nested_settings.push_back( aSettings );
747 }
748 
749 
ReleaseNestedSettings(NESTED_SETTINGS * aSettings)750 void JSON_SETTINGS::ReleaseNestedSettings( NESTED_SETTINGS* aSettings )
751 {
752     if( !aSettings )
753         return;
754 
755     auto it = std::find_if( m_nested_settings.begin(), m_nested_settings.end(),
756                             [&aSettings]( const JSON_SETTINGS* aPtr ) {
757                               return aPtr == aSettings;
758                             } );
759 
760     if( it != m_nested_settings.end() )
761     {
762         wxLogTrace( traceSettings, "Flush and release %s", ( *it )->GetFilename() );
763         ( *it )->SaveToFile();
764         m_nested_settings.erase( it );
765     }
766 
767     aSettings->SetParent( nullptr );
768 }
769 
770 
771 // Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
772 
Get(const std::string & aPath) const773 template<> OPT<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const
774 {
775     if( OPT<nlohmann::json> opt_json = GetJson( aPath ) )
776         return wxString( opt_json->get<std::string>().c_str(), wxConvUTF8 );
777 
778     return NULLOPT;
779 }
780 
781 
Set(const std::string & aPath,wxString aVal)782 template<> void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal )
783 {
784     ( *m_internals )[aPath] = aVal.ToUTF8();
785 }
786 
787 // Specializations to allow directly reading/writing wxStrings from JSON
788 
to_json(nlohmann::json & aJson,const wxString & aString)789 void to_json( nlohmann::json& aJson, const wxString& aString )
790 {
791     aJson = aString.ToUTF8();
792 }
793 
794 
from_json(const nlohmann::json & aJson,wxString & aString)795 void from_json( const nlohmann::json& aJson, wxString& aString )
796 {
797     aString = wxString( aJson.get<std::string>().c_str(), wxConvUTF8 );
798 }
799