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