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 {
450 wxCHECK2( settings, continue );
451
452 modified |= settings->SaveToFile();
453 }
454
455 modified |= Store();
456
457 if( !modified && !aForce && path.FileExists() )
458 {
459 wxLogTrace( traceSettings, wxT( "%s contents not modified, skipping save" ),
460 GetFullFilename() );
461 return false;
462 }
463 else if( !modified && !aForce && !m_createIfDefault )
464 {
465 wxLogTrace( traceSettings,
466 wxT( "%s contents still default and m_createIfDefault == false; not saving" ),
467 GetFullFilename() );
468 return false;
469 }
470
471 wxLogTrace( traceSettings, wxT( "Saving %s" ), GetFullFilename() );
472
474 bool success = true;
475
476 nlohmann::json toSave = m_internals->m_original;
477
478
479 for( PARAM_BASE* param : m_params )
480 {
481 if( param->ClearUnknownKeys() )
482 {
483 nlohmann::json_pointer p
484 = JSON_SETTINGS_INTERNALS::PointerFromString( param->GetJsonPath() );
485
486 toSave[p] = nlohmann::json( {} );
487 }
488 }
489
490 toSave.update( m_internals->begin(), m_internals->end(), /* merge_objects = */ true );
491
492 try
493 {
494 std::stringstream buffer;
495 buffer << std::setw( 2 ) << toSave << std::endl;
496
497 wxFFileOutputStream fileStream( path.GetFullPath(), "wb" );
498
499 if( !fileStream.IsOk()
500 || !fileStream.WriteAll( buffer.str().c_str(), buffer.str().size() ) )
501 {
502 wxLogTrace( traceSettings, wxT( "Warning: could not save %s" ), GetFullFilename() );
503 success = false;
504 }
505 }
506 catch( nlohmann::json::exception& error )
507 {
508 wxLogTrace( traceSettings, wxT( "Catch error: could not save %s. Json error %s" ),
509 GetFullFilename(), error.what() );
510 success = false;
511 }
512 catch( ... )
513 {
514 wxLogTrace( traceSettings, wxT( "Error: could not save %s." ) );
515 success = false;
516 }
517
518 if( success )
519 m_modified = false;
520
521 return success;
522}
523
524
526{
527 Store();
528
530
531 std::stringstream buffer;
532 buffer << std::setw( 2 ) << *m_internals << std::endl;
533
534 return buffer.str();
535}
536
537
538bool JSON_SETTINGS::LoadFromRawFile( const wxString& aPath )
539{
540 try
541 {
542 wxFFileInputStream fp( aPath, wxT( "rt" ) );
543 wxStdInputStream fstream( fp );
544
545 if( fp.IsOk() )
546 {
547 *static_cast<nlohmann::json*>( m_internals.get() ) =
548 nlohmann::json::parse( fstream, nullptr,
549 /* allow_exceptions = */ true,
550 /* ignore_comments = */ true );
551 }
552 else
553 {
554 return false;
555 }
556 }
557 catch( nlohmann::json::parse_error& error )
558 {
559 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
560
561 return false;
562 }
563
564 // Now that we have new data in the JSON structure, load the params again
565 Load();
566 return true;
567}
568
569
570std::optional<nlohmann::json> JSON_SETTINGS::GetJson( const std::string& aPath ) const
571{
572 nlohmann::json::json_pointer ptr = m_internals->PointerFromString( aPath );
573
574 if( m_internals->contains( ptr ) )
575 {
576 try
577 {
578 return std::optional<nlohmann::json>{ m_internals->at( ptr ) };
579 }
580 catch( ... )
581 {
582 }
583 }
584
585 return std::optional<nlohmann::json>{};
586}
587
588
589template<typename ValueType>
590std::optional<ValueType> JSON_SETTINGS::Get( const std::string& aPath ) const
591{
592 if( std::optional<nlohmann::json> ret = GetJson( aPath ) )
593 {
594 try
595 {
596 return ret->get<ValueType>();
597 }
598 catch( ... )
599 {
600 }
601 }
602
603 return std::nullopt;
604}
605
606
607// Instantiate all required templates here to allow reducing scope of json.hpp
608template KICOMMON_API std::optional<bool>
609 JSON_SETTINGS::Get<bool>( const std::string& aPath ) const;
610template KICOMMON_API std::optional<double>
611 JSON_SETTINGS::Get<double>( const std::string& aPath ) const;
612template KICOMMON_API std::optional<float>
613 JSON_SETTINGS::Get<float>( const std::string& aPath ) const;
614template KICOMMON_API std::optional<int> JSON_SETTINGS::Get<int>( const std::string& aPath ) const;
615template KICOMMON_API std::optional<unsigned int>
616 JSON_SETTINGS::Get<unsigned int>( const std::string& aPath ) const;
617template KICOMMON_API std::optional<unsigned long long>
618 JSON_SETTINGS::Get<unsigned long long>( const std::string& aPath ) const;
619template KICOMMON_API std::optional<std::string>
620 JSON_SETTINGS::Get<std::string>( const std::string& aPath ) const;
621template KICOMMON_API std::optional<nlohmann::json>
622 JSON_SETTINGS::Get<nlohmann::json>( const std::string& aPath ) const;
623template KICOMMON_API std::optional<KIGFX::COLOR4D>
624 JSON_SETTINGS::Get<KIGFX::COLOR4D>( const std::string& aPath ) const;
625template KICOMMON_API std::optional<BOM_FIELD>
626 JSON_SETTINGS::Get<BOM_FIELD>( const std::string& aPath ) const;
627template KICOMMON_API std::optional<BOM_PRESET>
628 JSON_SETTINGS::Get<BOM_PRESET>( const std::string& aPath ) const;
629template KICOMMON_API std::optional<BOM_FMT_PRESET>
630 JSON_SETTINGS::Get<BOM_FMT_PRESET>( const std::string& aPath ) const;
631template KICOMMON_API std::optional<GRID>
632 JSON_SETTINGS::Get<GRID>( const std::string& aPath ) const;
633template KICOMMON_API std::optional<wxPoint>
634 JSON_SETTINGS::Get<wxPoint>( const std::string& aPath ) const;
635template KICOMMON_API std::optional<wxSize>
636 JSON_SETTINGS::Get<wxSize>( const std::string& aPath ) const;
637template KICOMMON_API std::optional<wxRect>
638 JSON_SETTINGS::Get<wxRect>( const std::string& aPath ) const;
639template KICOMMON_API std::optional<wxAuiPaneInfo>
640 JSON_SETTINGS::Get<wxAuiPaneInfo>( const std::string& aPath ) const;
641
642template<typename ValueType>
643void JSON_SETTINGS::Set( const std::string& aPath, ValueType aVal )
644{
645 m_internals->SetFromString( aPath, std::move( aVal ) );
646}
647
648
649// Instantiate all required templates here to allow reducing scope of json.hpp
650template KICOMMON_API void JSON_SETTINGS::Set<bool>( const std::string& aPath, bool aValue );
651template KICOMMON_API void JSON_SETTINGS::Set<double>( const std::string& aPath, double aValue );
652template KICOMMON_API void JSON_SETTINGS::Set<float>( const std::string& aPath, float aValue );
653template KICOMMON_API void JSON_SETTINGS::Set<int>( const std::string& aPath, int aValue );
654template KICOMMON_API void JSON_SETTINGS::Set<unsigned int>( const std::string& aPath,
655 unsigned int aValue );
656template KICOMMON_API void JSON_SETTINGS::Set<unsigned long long>( const std::string& aPath,
657 unsigned long long aValue );
658template KICOMMON_API void JSON_SETTINGS::Set<const char*>( const std::string& aPath,
659 const char* aValue );
660template KICOMMON_API void JSON_SETTINGS::Set<std::string>( const std::string& aPath,
661 std::string aValue );
662template KICOMMON_API void JSON_SETTINGS::Set<nlohmann::json>( const std::string& aPath,
663 nlohmann::json aValue );
664template KICOMMON_API void JSON_SETTINGS::Set<KIGFX::COLOR4D>( const std::string& aPath,
665 KIGFX::COLOR4D aValue );
666template KICOMMON_API void JSON_SETTINGS::Set<BOM_FIELD>( const std::string& aPath,
667 BOM_FIELD aValue );
668template KICOMMON_API void JSON_SETTINGS::Set<BOM_PRESET>( const std::string& aPath,
669 BOM_PRESET aValue );
670template KICOMMON_API void JSON_SETTINGS::Set<BOM_FMT_PRESET>( const std::string& aPath,
671 BOM_FMT_PRESET aValue );
672template KICOMMON_API void JSON_SETTINGS::Set<GRID>( const std::string& aPath, GRID aValue );
673template KICOMMON_API void JSON_SETTINGS::Set<wxPoint>( const std::string& aPath, wxPoint aValue );
674template KICOMMON_API void JSON_SETTINGS::Set<wxSize>( const std::string& aPath, wxSize aValue );
675template KICOMMON_API void JSON_SETTINGS::Set<wxRect>( const std::string& aPath, wxRect aValue );
676template KICOMMON_API void JSON_SETTINGS::Set<wxAuiPaneInfo>( const std::string& aPath,
677 wxAuiPaneInfo aValue );
678
679
680void JSON_SETTINGS::registerMigration( int aOldSchemaVersion, int aNewSchemaVersion,
681 std::function<bool()> aMigrator )
682{
683 wxASSERT( aNewSchemaVersion > aOldSchemaVersion );
684 wxASSERT( aNewSchemaVersion <= m_schemaVersion );
685 m_migrators[aOldSchemaVersion] = std::make_pair( aNewSchemaVersion, aMigrator );
686}
687
688
690{
691 int filever = m_internals->Get<int>( "meta.version" );
692
693 while( filever < m_schemaVersion )
694 {
695 wxASSERT( m_migrators.count( filever ) > 0 );
696
697 if( !m_migrators.count( filever ) )
698 {
699 wxLogTrace( traceSettings, wxT( "Migrator missing for %s version %d!" ),
700 typeid( *this ).name(),
701 filever );
702 return false;
703 }
704
705 std::pair<int, std::function<bool()>> pair = m_migrators.at( filever );
706
707 if( pair.second() )
708 {
709 wxLogTrace( traceSettings, wxT( "Migrated %s from %d to %d" ),
710 typeid( *this ).name(),
711 filever,
712 pair.first );
713 filever = pair.first;
714 m_internals->At( "meta.version" ) = filever;
715 }
716 else
717 {
718 wxLogTrace( traceSettings, wxT( "Migration failed for %s from %d to %d" ),
719 typeid( *this ).name(),
720 filever,
721 pair.first );
722 return false;
723 }
724 }
725
726 return true;
727}
728
729
730bool JSON_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
731{
732 wxLogTrace( traceSettings, wxT( "MigrateFromLegacy() not implemented for %s" ),
733 typeid( *this ).name() );
734 return false;
735}
736
737
738bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
739 wxString& aTarget )
740{
741 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
742
743 if( aObj.contains( ptr ) && aObj.at( ptr ).is_string() )
744 {
745 aTarget = aObj.at( ptr ).get<wxString>();
746 return true;
747 }
748
749 return false;
750}
751
752
753bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
754 bool& aTarget )
755{
756 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
757
758 if( aObj.contains( ptr ) && aObj.at( ptr ).is_boolean() )
759 {
760 aTarget = aObj.at( ptr ).get<bool>();
761 return true;
762 }
763
764 return false;
765}
766
767
768bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
769 int& aTarget )
770{
771 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
772
773 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_integer() )
774 {
775 aTarget = aObj.at( ptr ).get<int>();
776 return true;
777 }
778
779 return false;
780}
781
782
783bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
784 unsigned int& aTarget )
785{
786 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
787
788 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_unsigned() )
789 {
790 aTarget = aObj.at( ptr ).get<unsigned int>();
791 return true;
792 }
793
794 return false;
795}
796
797
798template<typename ValueType>
799bool JSON_SETTINGS::fromLegacy( wxConfigBase* aConfig, const std::string& aKey,
800 const std::string& aDest )
801{
802 ValueType val;
803
804 if( aConfig->Read( aKey, &val ) )
805 {
806 try
807 {
808 ( *m_internals )[aDest] = val;
809 }
810 catch( ... )
811 {
812 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacy!" ) );
813 return false;
814 }
815
816 return true;
817 }
818
819 return false;
820}
821
822
823// Explicitly declare these because we only support a few types anyway, and it means we can keep
824// wxConfig detail out of the header file
825template
826KICOMMON_API bool JSON_SETTINGS::fromLegacy<int>( wxConfigBase*, const std::string&,
827 const std::string& );
828
829template
830KICOMMON_API bool JSON_SETTINGS::fromLegacy<double>( wxConfigBase*, const std::string&,
831 const std::string& );
832
833template
834KICOMMON_API bool JSON_SETTINGS::fromLegacy<bool>( wxConfigBase*, const std::string&,
835 const std::string& );
836
837
838bool JSON_SETTINGS::fromLegacyString( wxConfigBase* aConfig, const std::string& aKey,
839 const std::string& aDest )
840{
841 wxString str;
842
843 if( aConfig->Read( aKey, &str ) )
844 {
845 try
846 {
847 ( *m_internals )[aDest] = str.ToUTF8();
848 }
849 catch( ... )
850 {
851 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyString!" ) );
852 return false;
853 }
854
855 return true;
856 }
857
858 return false;
859}
860
861
862bool JSON_SETTINGS::fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
863 const std::string& aDest )
864{
865 wxString str;
866
867 if( aConfig->Read( aKey, &str ) )
868 {
870 color.SetFromWxString( str );
871
872 try
873 {
874 nlohmann::json js = nlohmann::json::array( { color.r, color.g, color.b, color.a } );
875 ( *m_internals )[aDest] = std::move( js );
876 }
877 catch( ... )
878 {
879 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyColor!" ) );
880 return false;
881 }
882
883 return true;
884 }
885
886 return false;
887}
888
889
891{
892 wxLogTrace( traceSettings, wxT( "AddNestedSettings %s" ), aSettings->GetFilename() );
893 m_nested_settings.push_back( aSettings );
894}
895
896
898{
899 if( !aSettings || !m_manager )
900 return;
901
902 auto it = std::find_if( m_nested_settings.begin(), m_nested_settings.end(),
903 [&aSettings]( const JSON_SETTINGS* aPtr )
904 {
905 return aPtr == aSettings;
906 } );
907
908 if( it != m_nested_settings.end() )
909 {
910 wxLogTrace( traceSettings, wxT( "Flush and release %s" ), ( *it )->GetFilename() );
911 m_modified |= ( *it )->SaveToFile();
912 m_nested_settings.erase( it );
913 }
914
915 aSettings->SetParent( nullptr );
916}
917
918
919// Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
920template<>
921std::optional<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const
922{
923 if( std::optional<nlohmann::json> opt_json = GetJson( aPath ) )
924 return wxString( opt_json->get<std::string>().c_str(), wxConvUTF8 );
925
926 return std::nullopt;
927}
928
929
930template<>
931void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal )
932{
933 ( *m_internals )[aPath] = aVal.ToUTF8();
934}
935
936
937template<typename ResultType>
938ResultType JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson, const std::string& aKey,
939 ResultType aDefault )
940{
941 ResultType ret = std::move( aDefault );
942
943 try
944 {
945 if( aJson.contains( aKey ) )
946 ret = aJson.at( aKey ).get<ResultType>();
947 }
948 catch( ... )
949 {
950 }
951
952 return ret;
953}
954
955
956template
957KICOMMON_API std::string JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
958 const std::string& aKey,
959 std::string aDefault );
960
961
962template
963KICOMMON_API bool JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
964 const std::string& aKey, bool aDefault );
int color
Definition: DXF_plotter.cpp:63
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:72
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:86
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:41
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