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