KiCad PCB EDA Suite
Loading...
Searching...
No Matches
json_settings.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 (C) 2020 Jon Evans <[email protected]>
5 * Copyright (C) 2020-2023 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>
32#include <settings/parameters.h>
36#include <wx/aui/framemanager.h>
37#include <wx/config.h>
38#include <wx/debug.h>
39#include <wx/fileconf.h>
40#include <wx/filename.h>
41#include <wx/gdicmn.h>
42#include <wx/log.h>
43#include <wx/stdstream.h>
44#include <wx/wfstream.h>
45
46const wxChar* const traceSettings = wxT( "KICAD_SETTINGS" );
47
48
49nlohmann::json::json_pointer JSON_SETTINGS_INTERNALS::PointerFromString( std::string aPath )
50{
51 std::replace( aPath.begin(), aPath.end(), '.', '/' );
52 aPath.insert( 0, "/" );
53
54 nlohmann::json::json_pointer p;
55
56 try
57 {
58 p = nlohmann::json::json_pointer( aPath );
59 }
60 catch( ... )
61 {
62 wxASSERT_MSG( false, wxT( "Invalid pointer path in PointerFromString!" ) );
63 }
64
65 return p;
66}
67
68
69JSON_SETTINGS::JSON_SETTINGS( const wxString& aFilename, SETTINGS_LOC aLocation,
70 int aSchemaVersion, bool aCreateIfMissing, bool aCreateIfDefault,
71 bool aWriteFile ) :
72 m_filename( aFilename ),
73 m_legacy_filename( "" ),
74 m_location( aLocation ),
75 m_createIfMissing( aCreateIfMissing ),
76 m_createIfDefault( aCreateIfDefault ),
77 m_writeFile( aWriteFile ),
78 m_deleteLegacyAfterMigration( true ),
79 m_resetParamsIfMissing( true ),
80 m_schemaVersion( aSchemaVersion ),
81 m_manager( nullptr )
82{
83 m_internals = std::make_unique<JSON_SETTINGS_INTERNALS>();
84
85 try
86 {
87 m_internals->SetFromString( "meta.filename", GetFullFilename() );
88 }
89 catch( ... )
90 {
91 wxLogTrace( traceSettings, wxT( "Error: Could not create filename field for %s" ),
93 }
94
95
96 m_params.emplace_back(
97 new PARAM<int>( "meta.version", &m_schemaVersion, m_schemaVersion, true ) );
98}
99
100
102{
103 for( auto param: m_params )
104 delete param;
105
106 m_params.clear();
107}
108
109
111{
112 return wxString( m_filename + "." + getFileExt() );
113}
114
115
116nlohmann::json& JSON_SETTINGS::At( const std::string& aPath )
117{
118 return m_internals->At( aPath );
119}
120
121
122bool JSON_SETTINGS::Contains( const std::string& aPath ) const
123{
124 return m_internals->contains( JSON_SETTINGS_INTERNALS::PointerFromString( aPath ) );
125}
126
127
129{
130 return m_internals.get();
131}
132
133
135{
136 for( auto param : m_params )
137 {
138 try
139 {
140 param->Load( this, m_resetParamsIfMissing );
141 }
142 catch( ... )
143 {
144 // Skip unreadable parameters in file
145 wxLogTrace( traceSettings, wxT( "param '%s' load err" ), param->GetJsonPath().c_str() );
146 }
147 }
148}
149
150
151bool JSON_SETTINGS::LoadFromFile( const wxString& aDirectory )
152{
153 // First, load all params to default values
154 m_internals->clear();
155 Load();
156
157 bool success = true;
158 bool migrated = false;
159 bool legacy_migrated = false;
160
161 LOCALE_IO locale;
162
163 auto migrateFromLegacy = [&] ( wxFileName& aPath ) {
164 // Backup and restore during migration so that the original can be mutated if convenient
165 bool backed_up = false;
166 wxFileName temp;
167
168 if( aPath.IsDirWritable() )
169 {
170 temp.AssignTempFileName( aPath.GetFullPath() );
171
172 if( !wxCopyFile( aPath.GetFullPath(), temp.GetFullPath() ) )
173 {
174 wxLogTrace( traceSettings, wxT( "%s: could not create temp file for migration" ),
175 GetFullFilename() );
176 }
177 else
178 backed_up = true;
179 }
180
181 // Silence popups if legacy file is read-only
182 wxLogNull doNotLog;
183
184 wxConfigBase::DontCreateOnDemand();
185 auto cfg = std::make_unique<wxFileConfig>( wxT( "" ), wxT( "" ), aPath.GetFullPath() );
186
187 // If migrate fails or is not implemented, fall back to built-in defaults that were
188 // already loaded above
189 if( !MigrateFromLegacy( cfg.get() ) )
190 {
191 success = false;
192 wxLogTrace( traceSettings,
193 wxT( "%s: migrated; not all settings were found in legacy file" ),
194 GetFullFilename() );
195 }
196 else
197 {
198 success = true;
199 wxLogTrace( traceSettings, wxT( "%s: migrated from legacy format" ), GetFullFilename() );
200 }
201
202 if( backed_up )
203 {
204 cfg.reset();
205
206 if( !wxCopyFile( temp.GetFullPath(), aPath.GetFullPath() ) )
207 {
208 wxLogTrace( traceSettings,
209 wxT( "migrate; copy temp file %s to %s failed" ),
210 temp.GetFullPath(), aPath.GetFullPath() );
211 }
212
213 if( !wxRemoveFile( temp.GetFullPath() ) )
214 {
215 wxLogTrace( traceSettings,
216 wxT( "migrate; failed to remove temp file %s" ),
217 temp.GetFullPath() );
218 }
219 }
220
221 // Either way, we want to clean up the old file afterwards
222 legacy_migrated = true;
223 };
224
225 wxFileName path;
226
227 if( aDirectory.empty() )
228 {
229 path.Assign( m_filename );
230 path.SetExt( getFileExt() );
231 }
232 else
233 {
234 wxString dir( aDirectory );
235 path.Assign( dir, m_filename, getFileExt() );
236 }
237
238 if( !path.Exists() )
239 {
240 // Case 1: legacy migration, no .json extension yet
241 path.SetExt( getLegacyFileExt() );
242
243 if( path.Exists() )
244 {
245 migrateFromLegacy( path );
246 }
247 // Case 2: legacy filename is different from new one
248 else if( !m_legacy_filename.empty() )
249 {
250 path.SetName( m_legacy_filename );
251
252 if( path.Exists() )
253 migrateFromLegacy( path );
254 }
255 else
256 {
257 success = false;
258 }
259 }
260 else
261 {
262 if( !path.IsFileWritable() )
263 m_writeFile = false;
264
265 try
266 {
267 wxFFileInputStream fp( path.GetFullPath(), wxT( "rt" ) );
268 wxStdInputStream fstream( fp );
269
270 if( fp.IsOk() )
271 {
272 *static_cast<nlohmann::json*>( m_internals.get() ) =
273 nlohmann::json::parse( fstream, nullptr,
274 /* allow_exceptions = */ true,
275 /* ignore_comments = */ true );
276
277 // Save whatever we loaded, before doing any migration etc
278 m_internals->m_original = *static_cast<nlohmann::json*>( m_internals.get() );
279
280 // If parse succeeds, check if schema migration is required
281 int filever = -1;
282
283 try
284 {
285 filever = m_internals->Get<int>( "meta.version" );
286 }
287 catch( ... )
288 {
289 wxLogTrace( traceSettings, wxT( "%s: file version could not be read!" ),
290 GetFullFilename() );
291 success = false;
292 }
293
294 if( filever >= 0 && filever < m_schemaVersion )
295 {
296 wxLogTrace( traceSettings, wxT( "%s: attempting migration from version %d to %d" ),
297 GetFullFilename(), filever, m_schemaVersion );
298
299 if( Migrate() )
300 {
301 migrated = true;
302 }
303 else
304 {
305 wxLogTrace( traceSettings, wxT( "%s: migration failed!" ), GetFullFilename() );
306 }
307 }
308 else if( filever > m_schemaVersion )
309 {
310 wxLogTrace( traceSettings,
311 wxT( "%s: warning: file version %d is newer than latest (%d)" ),
312 GetFullFilename(), filever, m_schemaVersion );
313 }
314 }
315 else
316 {
317 wxLogTrace( traceSettings, wxT( "%s exists but can't be opened for read" ),
318 GetFullFilename() );
319 }
320 }
321 catch( nlohmann::json::parse_error& error )
322 {
323 success = false;
324 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ),
325 path.GetFullPath(), error.what() );
326 wxLogTrace( traceSettings, wxT( "Attempting migration in case file is in legacy format" ) );
327 migrateFromLegacy( path );
328 }
329 }
330
331 // Now that we have new data in the JSON structure, load the params again
332 Load();
333
334 // And finally load any nested settings
335 for( auto settings : m_nested_settings )
336 settings->LoadFromFile();
337
338 wxLogTrace( traceSettings, wxT( "Loaded <%s> with schema %d" ), GetFullFilename(), m_schemaVersion );
339
340 // If we migrated, clean up the legacy file (with no extension)
341 if( m_writeFile && ( legacy_migrated || migrated ) )
342 {
343 if( legacy_migrated && m_deleteLegacyAfterMigration && !wxRemoveFile( path.GetFullPath() ) )
344 {
345 wxLogTrace( traceSettings, wxT( "Warning: could not remove legacy file %s" ),
346 path.GetFullPath() );
347 }
348
349 // And write-out immediately so that we don't lose data if the program later crashes.
351 SaveToFile( aDirectory, true );
352 }
353
354 return success;
355}
356
357
359{
360 bool modified = false;
361
362 for( auto param : m_params )
363 {
364 modified |= !param->MatchesFile( this );
365 param->Store( this );
366 }
367
368 return modified;
369}
370
371
373{
374 for( auto param : m_params )
375 param->SetDefault();
376}
377
378
379bool JSON_SETTINGS::SaveToFile( const wxString& aDirectory, bool aForce )
380{
381 if( !m_writeFile )
382 return false;
383
384 // Default PROJECT won't have a filename set
385 if( m_filename.IsEmpty() )
386 return false;
387
388 wxFileName path;
389
390 if( aDirectory.empty() )
391 {
392 path.Assign( m_filename );
393 path.SetExt( getFileExt() );
394 }
395 else
396 {
397 wxString dir( aDirectory );
398 path.Assign( dir, m_filename, getFileExt() );
399 }
400
401 if( !m_createIfMissing && !path.FileExists() )
402 {
403 wxLogTrace( traceSettings,
404 wxT( "File for %s doesn't exist and m_createIfMissing == false; not saving" ),
405 GetFullFilename() );
406 return false;
407 }
408
409 // Ensure the path exists, and create it if not.
410 if( !path.DirExists() && !path.Mkdir() )
411 {
412 wxLogTrace( traceSettings, wxT( "Warning: could not create path %s, can't save %s" ),
413 path.GetPath(), GetFullFilename() );
414 return false;
415 }
416
417 if( ( path.FileExists() && !path.IsFileWritable() ) ||
418 ( !path.FileExists() && !path.IsDirWritable() ) )
419 {
420 wxLogTrace( traceSettings, wxT( "File for %s is read-only; not saving" ), GetFullFilename() );
421 return false;
422 }
423
424 bool modified = false;
425
426 for( auto settings : m_nested_settings )
427 modified |= settings->SaveToFile();
428
429 modified |= Store();
430
431 if( !modified && !aForce && path.FileExists() )
432 {
433 wxLogTrace( traceSettings, wxT( "%s contents not modified, skipping save" ), GetFullFilename() );
434 return false;
435 }
436 else if( !modified && !aForce && !m_createIfDefault )
437 {
438 wxLogTrace( traceSettings,
439 wxT( "%s contents still default and m_createIfDefault == false; not saving" ),
440 GetFullFilename() );
441 return false;
442 }
443
444 wxLogTrace( traceSettings, wxT( "Saving %s" ), GetFullFilename() );
445
447 bool success = true;
448
449 nlohmann::json toSave = m_internals->m_original;
450
451 toSave.update( m_internals->begin(), m_internals->end(), /* merge_objects = */ true );
452
453 try
454 {
455 std::stringstream buffer;
456 buffer << std::setw( 2 ) << toSave << std::endl;
457
458 wxFFileOutputStream fileStream( path.GetFullPath(), "wb" );
459
460 if( !fileStream.IsOk()
461 || !fileStream.WriteAll( buffer.str().c_str(), buffer.str().size() ) )
462 {
463 wxLogTrace( traceSettings, wxT( "Warning: could not save %s" ), GetFullFilename() );
464 success = false;
465 }
466 }
467 catch( nlohmann::json::exception& error )
468 {
469 wxLogTrace( traceSettings, wxT( "Catch error: could not save %s. Json error %s" ),
470 GetFullFilename(), error.what() );
471 success = false;
472 }
473 catch( ... )
474 {
475 wxLogTrace( traceSettings, wxT( "Error: could not save %s." ) );
476 success = false;
477 }
478
479 return success;
480}
481
482
484{
485 Store();
486
488
489 std::stringstream buffer;
490 buffer << std::setw( 2 ) << *m_internals << std::endl;
491
492 return buffer.str();
493}
494
495
496bool JSON_SETTINGS::LoadFromRawFile( const wxString& aPath )
497{
498 try
499 {
500 wxFFileInputStream fp( aPath, wxT( "rt" ) );
501 wxStdInputStream fstream( fp );
502
503 if( fp.IsOk() )
504 {
505 *static_cast<nlohmann::json*>( m_internals.get() ) =
506 nlohmann::json::parse( fstream, nullptr,
507 /* allow_exceptions = */ true,
508 /* ignore_comments = */ true );
509 }
510 else
511 {
512 return false;
513 }
514 }
515 catch( nlohmann::json::parse_error& error )
516 {
517 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
518
519 return false;
520 }
521
522 // Now that we have new data in the JSON structure, load the params again
523 Load();
524 return true;
525}
526
527
528std::optional<nlohmann::json> JSON_SETTINGS::GetJson( const std::string& aPath ) const
529{
530 nlohmann::json::json_pointer ptr = m_internals->PointerFromString( aPath );
531
532 if( m_internals->contains( ptr ) )
533 {
534 try
535 {
536 return std::optional<nlohmann::json>{ m_internals->at( ptr ) };
537 }
538 catch( ... )
539 {
540 }
541 }
542
543 return std::optional<nlohmann::json>{};
544}
545
546
547template<typename ValueType>
548std::optional<ValueType> JSON_SETTINGS::Get( const std::string& aPath ) const
549{
550 if( std::optional<nlohmann::json> ret = GetJson( aPath ) )
551 {
552 try
553 {
554 return ret->get<ValueType>();
555 }
556 catch( ... )
557 {
558 }
559 }
560
561 return std::nullopt;
562}
563
564
565// Instantiate all required templates here to allow reducing scope of json.hpp
566template std::optional<bool> JSON_SETTINGS::Get<bool>( const std::string& aPath ) const;
567template std::optional<double> JSON_SETTINGS::Get<double>( const std::string& aPath ) const;
568template std::optional<float> JSON_SETTINGS::Get<float>( const std::string& aPath ) const;
569template std::optional<int> JSON_SETTINGS::Get<int>( const std::string& aPath ) const;
570template std::optional<unsigned int> JSON_SETTINGS::Get<unsigned int>( const std::string& aPath ) const;
571template std::optional<unsigned long long> JSON_SETTINGS::Get<unsigned long long>( const std::string& aPath ) const;
572template std::optional<std::string> JSON_SETTINGS::Get<std::string>( const std::string& aPath ) const;
573template std::optional<nlohmann::json> JSON_SETTINGS::Get<nlohmann::json>( const std::string& aPath ) const;
574template std::optional<KIGFX::COLOR4D> JSON_SETTINGS::Get<KIGFX::COLOR4D>( const std::string& aPath ) const;
575template std::optional<BOM_FIELD> JSON_SETTINGS::Get<BOM_FIELD>( const std::string& aPath ) const;
576template std::optional<BOM_PRESET> JSON_SETTINGS::Get<BOM_PRESET>( const std::string& aPath ) const;
577template std::optional<BOM_FMT_PRESET> JSON_SETTINGS::Get<BOM_FMT_PRESET>( const std::string& aPath ) const;
578template std::optional<GRID> JSON_SETTINGS::Get<GRID>( const std::string& aPath ) const;
579template std::optional<wxPoint> JSON_SETTINGS::Get<wxPoint>( const std::string& aPath ) const;
580template std::optional<wxSize> JSON_SETTINGS::Get<wxSize>( const std::string& aPath ) const;
581template std::optional<wxRect> JSON_SETTINGS::Get<wxRect>( const std::string& aPath ) const;
582template std::optional<wxAuiPaneInfo> JSON_SETTINGS::Get<wxAuiPaneInfo>( const std::string& aPath ) const;
583
584template<typename ValueType>
585void JSON_SETTINGS::Set( const std::string& aPath, ValueType aVal )
586{
587 m_internals->SetFromString( aPath, aVal );
588}
589
590
591// Instantiate all required templates here to allow reducing scope of json.hpp
592template void JSON_SETTINGS::Set<bool>( const std::string& aPath, bool aValue );
593template void JSON_SETTINGS::Set<double>( const std::string& aPath, double aValue );
594template void JSON_SETTINGS::Set<float>( const std::string& aPath, float aValue );
595template void JSON_SETTINGS::Set<int>( const std::string& aPath, int aValue );
596template void JSON_SETTINGS::Set<unsigned int>( const std::string& aPath, unsigned int aValue );
597template void JSON_SETTINGS::Set<unsigned long long>( const std::string& aPath, unsigned long long aValue );
598template void JSON_SETTINGS::Set<const char*>( const std::string& aPath, const char* aValue );
599template void JSON_SETTINGS::Set<std::string>( const std::string& aPath, std::string aValue );
600template void JSON_SETTINGS::Set<nlohmann::json>( const std::string& aPath, nlohmann::json aValue );
601template void JSON_SETTINGS::Set<KIGFX::COLOR4D>( const std::string& aPath, KIGFX::COLOR4D aValue );
602template void JSON_SETTINGS::Set<BOM_FIELD>( const std::string& aPath, BOM_FIELD aValue );
603template void JSON_SETTINGS::Set<BOM_PRESET>( const std::string& aPath, BOM_PRESET aValue );
604template void JSON_SETTINGS::Set<BOM_FMT_PRESET>( const std::string& aPath, BOM_FMT_PRESET aValue );
605template void JSON_SETTINGS::Set<GRID>( const std::string& aPath, GRID aValue );
606template void JSON_SETTINGS::Set<wxPoint>( const std::string& aPath, wxPoint aValue );
607template void JSON_SETTINGS::Set<wxSize>( const std::string& aPath, wxSize aValue );
608template void JSON_SETTINGS::Set<wxRect>( const std::string& aPath, wxRect aValue );
609template void JSON_SETTINGS::Set<wxAuiPaneInfo>( const std::string& aPath, wxAuiPaneInfo aValue );
610
611
612void JSON_SETTINGS::registerMigration( int aOldSchemaVersion, int aNewSchemaVersion,
613 std::function<bool()> aMigrator )
614{
615 wxASSERT( aNewSchemaVersion > aOldSchemaVersion );
616 wxASSERT( aNewSchemaVersion <= m_schemaVersion );
617 m_migrators[aOldSchemaVersion] = std::make_pair( aNewSchemaVersion, aMigrator );
618}
619
620
622{
623 int filever = m_internals->Get<int>( "meta.version" );
624
625 while( filever < m_schemaVersion )
626 {
627 if( !m_migrators.count( filever ) )
628 {
629 wxLogTrace( traceSettings, wxT( "Migrator missing for %s version %d!" ),
630 typeid( *this ).name(), filever );
631 return false;
632 }
633
634 std::pair<int, std::function<bool()>> pair = m_migrators.at( filever );
635
636 if( pair.second() )
637 {
638 wxLogTrace( traceSettings, wxT( "Migrated %s from %d to %d" ), typeid( *this ).name(),
639 filever, pair.first );
640 filever = pair.first;
641 m_internals->At( "meta.version" ) = filever;
642 }
643 else
644 {
645 wxLogTrace( traceSettings, wxT( "Migration failed for %s from %d to %d" ),
646 typeid( *this ).name(), filever, pair.first );
647 return false;
648 }
649 }
650
651 return true;
652}
653
654
655bool JSON_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
656{
657 wxLogTrace( traceSettings,
658 wxT( "MigrateFromLegacy() not implemented for %s" ), typeid( *this ).name() );
659 return false;
660}
661
662
663bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
664 wxString& aTarget )
665{
666 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
667
668 if( aObj.contains( ptr ) && aObj.at( ptr ).is_string() )
669 {
670 aTarget = aObj.at( ptr ).get<wxString>();
671 return true;
672 }
673
674 return false;
675}
676
677
678bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
679 bool& aTarget )
680{
681 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
682
683 if( aObj.contains( ptr ) && aObj.at( ptr ).is_boolean() )
684 {
685 aTarget = aObj.at( ptr ).get<bool>();
686 return true;
687 }
688
689 return false;
690}
691
692
693bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
694 int& aTarget )
695{
696 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
697
698 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_integer() )
699 {
700 aTarget = aObj.at( ptr ).get<int>();
701 return true;
702 }
703
704 return false;
705}
706
707
708bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
709 unsigned int& aTarget )
710{
711 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
712
713 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_unsigned() )
714 {
715 aTarget = aObj.at( ptr ).get<unsigned int>();
716 return true;
717 }
718
719 return false;
720}
721
722
723template<typename ValueType>
724bool JSON_SETTINGS::fromLegacy( wxConfigBase* aConfig, const std::string& aKey,
725 const std::string& aDest )
726{
727 ValueType val;
728
729 if( aConfig->Read( aKey, &val ) )
730 {
731 try
732 {
733 ( *m_internals )[aDest] = val;
734 }
735 catch( ... )
736 {
737 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacy!" ) );
738 return false;
739 }
740
741 return true;
742 }
743
744 return false;
745}
746
747
748// Explicitly declare these because we only support a few types anyway, and it means we can keep
749// wxConfig detail out of the header file
750template bool JSON_SETTINGS::fromLegacy<int>( wxConfigBase*, const std::string&,
751 const std::string& );
752
753template bool JSON_SETTINGS::fromLegacy<double>( wxConfigBase*, const std::string&,
754 const std::string& );
755
756template bool JSON_SETTINGS::fromLegacy<bool>( wxConfigBase*, const std::string&,
757 const std::string& );
758
759
760bool JSON_SETTINGS::fromLegacyString( wxConfigBase* aConfig, const std::string& aKey,
761 const std::string& aDest )
762{
763 wxString str;
764
765 if( aConfig->Read( aKey, &str ) )
766 {
767 try
768 {
769 ( *m_internals )[aDest] = str.ToUTF8();
770 }
771 catch( ... )
772 {
773 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyString!" ) );
774 return false;
775 }
776
777 return true;
778 }
779
780 return false;
781}
782
783
784bool JSON_SETTINGS::fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
785 const std::string& aDest )
786{
787 wxString str;
788
789 if( aConfig->Read( aKey, &str ) )
790 {
792 color.SetFromWxString( str );
793
794 try
795 {
796 nlohmann::json js = nlohmann::json::array( { color.r, color.g, color.b, color.a } );
797 ( *m_internals )[aDest] = js;
798 }
799 catch( ... )
800 {
801 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyColor!" ) );
802 return false;
803 }
804
805 return true;
806 }
807
808 return false;
809}
810
811
813{
814 wxLogTrace( traceSettings, wxT( "AddNestedSettings %s" ), aSettings->GetFilename() );
815 m_nested_settings.push_back( aSettings );
816}
817
818
820{
821 if( !aSettings || !m_manager )
822 return;
823
824 auto it = std::find_if( m_nested_settings.begin(), m_nested_settings.end(),
825 [&aSettings]( const JSON_SETTINGS* aPtr ) {
826 return aPtr == aSettings;
827 } );
828
829 if( it != m_nested_settings.end() )
830 {
831 wxLogTrace( traceSettings, wxT( "Flush and release %s" ), ( *it )->GetFilename() );
832 ( *it )->SaveToFile();
833 m_nested_settings.erase( it );
834 }
835
836 aSettings->SetParent( nullptr );
837}
838
839
840// Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
841
842template<> std::optional<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const
843{
844 if( std::optional<nlohmann::json> opt_json = GetJson( aPath ) )
845 return wxString( opt_json->get<std::string>().c_str(), wxConvUTF8 );
846
847 return std::nullopt;
848}
849
850
851template<> void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal )
852{
853 ( *m_internals )[aPath] = aVal.ToUTF8();
854}
855
856// Specializations to allow directly reading/writing wxStrings from JSON
857
858void to_json( nlohmann::json& aJson, const wxString& aString )
859{
860 aJson = aString.ToUTF8();
861}
862
863
864void from_json( const nlohmann::json& aJson, wxString& aString )
865{
866 aString = wxString( aJson.get<std::string>().c_str(), wxConvUTF8 );
867}
868
869
870template<typename ResultType>
871ResultType JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson, const std::string& aKey,
872 ResultType aDefault )
873{
874 ResultType ret = aDefault;
875
876 try
877 {
878 if( aJson.contains( aKey ) )
879 ret = aJson.at( aKey ).get<ResultType>();
880 }
881 catch( ... )
882 {
883 }
884
885 return ret;
886}
887
888
889template std::string JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
890 const std::string& aKey, std::string aDefault );
891
892template bool JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson, const std::string& aKey,
893 bool aDefault );
int color
Definition: DXF_plotter.cpp:58
static nlohmann::json::json_pointer PointerFromString(std::string aPath)
Builds a JSON pointer based on a given string.
bool fromLegacyString(wxConfigBase *aConfig, const std::string &aKey, const std::string &aDest)
Translates a legacy wxConfig string value to a given JSON pointer value.
virtual wxString getFileExt() const
std::optional< ValueType > Get(const std::string &aPath) const
Fetches a value from within the JSON document.
std::optional< nlohmann::json > GetJson(const std::string &aPath) const
Fetches a JSON object that is a subset of this JSON_SETTINGS object, using a path of the form "key1....
bool fromLegacy(wxConfigBase *aConfig, const std::string &aKey, const std::string &aDest)
Translates a legacy wxConfig value to a given JSON pointer value.
wxString m_filename
The filename (not including path) of this settings file (inicode)
SETTINGS_MANAGER * m_manager
A pointer to the settings manager managing this file (may be null)
bool Contains(const std::string &aPath) const
bool LoadFromRawFile(const wxString &aPath)
virtual ~JSON_SETTINGS()
std::vector< NESTED_SETTINGS * > m_nested_settings
Nested settings files that live inside this one, if any.
virtual bool LoadFromFile(const wxString &aDirectory="")
Loads the backing file from disk and then calls Load()
bool m_createIfDefault
Whether or not the backing store file should be created if all parameters are still at their default ...
bool m_writeFile
Whether or not the backing store file should be written.
static bool SetIfPresent(const nlohmann::json &aObj, const std::string &aPath, wxString &aTarget)
Sets the given string if the given key/path is present.
virtual void Load()
Updates the parameters of this object based on the current JSON document contents.
void ResetToDefaults()
Resets all parameters to default values.
bool fromLegacyColor(wxConfigBase *aConfig, const std::string &aKey, const std::string &aDest)
Translates a legacy COLOR4D stored in a wxConfig string to a given JSON pointer value.
wxString GetFullFilename() const
bool m_createIfMissing
Whether or not the backing store file should be created it if doesn't exist.
bool Migrate()
Migrates the schema of this settings from the version in the file to the latest version.
std::vector< PARAM_BASE * > m_params
The list of parameters (owned by this object)
static ResultType fetchOrDefault(const nlohmann::json &aJson, const std::string &aKey, ResultType aDefault=ResultType())
Helper to retrieve a value from a JSON object (dictionary) as a certain result type.
virtual bool MigrateFromLegacy(wxConfigBase *aLegacyConfig)
Migrates from wxConfig to JSON-based configuration.
const std::string FormatAsString()
void registerMigration(int aOldSchemaVersion, int aNewSchemaVersion, std::function< bool(void)> aMigrator)
Registers a migration from one schema version to another.
nlohmann::json & At(const std::string &aPath)
Wrappers for the underlying JSON API so that most consumers don't need json.hpp All of these function...
JSON_SETTINGS_INTERNALS * Internals()
void ReleaseNestedSettings(NESTED_SETTINGS *aSettings)
Saves and frees a nested settings object, if it exists within this one.
void Set(const std::string &aPath, ValueType aVal)
Stores a value into the JSON document Will throw an exception if ValueType isn't something that the l...
JSON_SETTINGS(const wxString &aFilename, SETTINGS_LOC aLocation, int aSchemaVersion)
Definition: json_settings.h:64
bool m_deleteLegacyAfterMigration
Whether or not to delete legacy file after migration.
std::unique_ptr< JSON_SETTINGS_INTERNALS > m_internals
virtual bool SaveToFile(const wxString &aDirectory="", bool aForce=false)
virtual wxString getLegacyFileExt() const
bool m_resetParamsIfMissing
Whether or not to set parameters to their default value if missing from JSON on Load()
std::map< int, std::pair< int, std::function< bool()> > > m_migrators
A map of starting schema version to a pair of <ending version, migrator function>
int m_schemaVersion
Version of this settings schema.
wxString m_legacy_filename
The filename of the wxConfig legacy file (if different from m_filename)
void AddNestedSettings(NESTED_SETTINGS *aSettings)
Transfers ownership of a given NESTED_SETTINGS to this object.
virtual bool Store()
Stores the current parameters into the JSON document represented by this object Note: this doesn't do...
wxString GetFilename() const
Definition: json_settings.h:73
A color representation with 4 components: red, green, blue, alpha.
Definition: color4d.h:104
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition: locale_io.h:49
NESTED_SETTINGS is a JSON_SETTINGS that lives inside a JSON_SETTINGS.
void SetParent(JSON_SETTINGS *aParent, bool aLoadFromFile=true)
const wxChar *const traceSettings
Flag to enable debug output of settings operations and management.
void from_json(const nlohmann::json &aJson, wxString &aString)
void to_json(nlohmann::json &aJson, const wxString &aString)
SETTINGS_LOC
Definition: json_settings.h:47
std::vector< FAB_LAYER_COLOR > dummy
Common grid settings, available to every frame.
Definition: grid_settings.h:34