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_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( new PARAM<int>( "meta.version", &m_schemaVersion, m_schemaVersion,
96 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 path.Assign( aDirectory, m_filename, getFileExt() );
246 }
247
248 if( !path.Exists() )
249 {
250 // Case 1: legacy migration, no .json extension yet
251 path.SetExt( getLegacyFileExt() );
252
253 if( path.Exists() )
254 {
255 migrateFromLegacy( path );
256 }
257 // Case 2: legacy filename is different from new one
258 else if( !m_legacy_filename.empty() )
259 {
260 path.SetName( m_legacy_filename );
261
262 if( path.Exists() )
263 migrateFromLegacy( path );
264 }
265 else
266 {
267 success = false;
268 }
269 }
270 else
271 {
272 if( !path.IsFileWritable() )
273 m_writeFile = false;
274
275 try
276 {
277 wxFFileInputStream fp( path.GetFullPath(), wxT( "rt" ) );
278 wxStdInputStream fstream( fp );
279
280 if( fp.IsOk() )
281 {
282 *static_cast<nlohmann::json*>( m_internals.get() ) =
283 nlohmann::json::parse( fstream, nullptr,
284 /* allow_exceptions = */ true,
285 /* ignore_comments = */ true );
286
287 // Save whatever we loaded, before doing any migration etc
288 m_internals->m_original = *static_cast<nlohmann::json*>( m_internals.get() );
289
290 // If parse succeeds, check if schema migration is required
291 int filever = -1;
292
293 try
294 {
295 filever = m_internals->Get<int>( "meta.version" );
296 }
297 catch( ... )
298 {
299 wxLogTrace( traceSettings, wxT( "%s: file version could not be read!" ),
300 GetFullFilename() );
301 success = false;
302 }
303
304 if( filever >= 0 && filever < m_schemaVersion )
305 {
306 wxLogTrace( traceSettings, wxT( "%s: attempting migration from version "
307 "%d to %d" ),
309 filever,
311
312 if( Migrate() )
313 {
314 migrated = true;
315 }
316 else
317 {
318 wxLogTrace( traceSettings, wxT( "%s: migration failed!" ),
319 GetFullFilename() );
320 }
321 }
322 else if( filever > m_schemaVersion )
323 {
324 wxLogTrace( traceSettings,
325 wxT( "%s: warning: file version %d is newer than latest (%d)" ),
327 filever,
329 }
330 }
331 else
332 {
333 wxLogTrace( traceSettings, wxT( "%s exists but can't be opened for read" ),
334 GetFullFilename() );
335 }
336 }
337 catch( nlohmann::json::parse_error& error )
338 {
339 success = false;
340 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ),
341 path.GetFullPath(), error.what() );
342 wxLogTrace( traceSettings, wxT( "Attempting migration in case file is in legacy "
343 "format" ) );
344 migrateFromLegacy( path );
345 }
346 }
347
348 // Now that we have new data in the JSON structure, load the params again
349 Load();
350
351 // And finally load any nested settings
352 for( NESTED_SETTINGS* settings : m_nested_settings )
353 settings->LoadFromFile();
354
355 wxLogTrace( traceSettings, wxT( "Loaded <%s> with schema %d" ),
358
359 m_modified = false;
360
361 // If we migrated, clean up the legacy file (with no extension)
362 if( m_writeFile && ( legacy_migrated || migrated ) )
363 {
364 if( legacy_migrated && m_deleteLegacyAfterMigration && !wxRemoveFile( path.GetFullPath() ) )
365 {
366 wxLogTrace( traceSettings, wxT( "Warning: could not remove legacy file %s" ),
367 path.GetFullPath() );
368 }
369
370 // And write-out immediately so that we don't lose data if the program later crashes.
372 SaveToFile( aDirectory, true );
373 }
374
375 return success;
376}
377
378
380{
381 for( PARAM_BASE* param : m_params )
382 {
383 m_modified |= !param->MatchesFile( *this );
384 param->Store( this );
385 }
386
387 return m_modified;
388}
389
390
392{
393 for( PARAM_BASE* param : m_params )
394 param->SetDefault();
395}
396
397
398bool JSON_SETTINGS::SaveToFile( const wxString& aDirectory, bool aForce )
399{
400 if( !m_writeFile )
401 return false;
402
403 // Default PROJECT won't have a filename set
404 if( m_filename.IsEmpty() )
405 return false;
406
407 wxFileName path;
408
409 if( aDirectory.empty() )
410 {
411 path.Assign( m_filename );
412 path.SetExt( getFileExt() );
413 }
414 else
415 {
416 wxString dir( aDirectory );
417 path.Assign( dir, m_filename, getFileExt() );
418 }
419
420 if( !m_createIfMissing && !path.FileExists() )
421 {
422 wxLogTrace( traceSettings,
423 wxT( "File for %s doesn't exist and m_createIfMissing == false; not saving" ),
424 GetFullFilename() );
425 return false;
426 }
427
428 // Ensure the path exists, and create it if not.
429 if( !path.DirExists() && !path.Mkdir() )
430 {
431 wxLogTrace( traceSettings, wxT( "Warning: could not create path %s, can't save %s" ),
432 path.GetPath(), GetFullFilename() );
433 return false;
434 }
435
436 if( ( path.FileExists() && !path.IsFileWritable() ) ||
437 ( !path.FileExists() && !path.IsDirWritable() ) )
438 {
439 wxLogTrace( traceSettings, wxT( "File for %s is read-only; not saving" ),
440 GetFullFilename() );
441 return false;
442 }
443
444 bool modified = false;
445
446 for( NESTED_SETTINGS* settings : m_nested_settings )
447 modified |= settings->SaveToFile();
448
449 modified |= Store();
450
451 if( !modified && !aForce && path.FileExists() )
452 {
453 wxLogTrace( traceSettings, wxT( "%s contents not modified, skipping save" ),
454 GetFullFilename() );
455 return false;
456 }
457 else if( !modified && !aForce && !m_createIfDefault )
458 {
459 wxLogTrace( traceSettings,
460 wxT( "%s contents still default and m_createIfDefault == false; not saving" ),
461 GetFullFilename() );
462 return false;
463 }
464
465 wxLogTrace( traceSettings, wxT( "Saving %s" ), GetFullFilename() );
466
468 bool success = true;
469
470 nlohmann::json toSave = m_internals->m_original;
471
472
473 for( PARAM_BASE* param : m_params )
474 {
475 if( param->ClearUnknownKeys() )
476 {
477 nlohmann::json_pointer p
478 = JSON_SETTINGS_INTERNALS::PointerFromString( param->GetJsonPath() );
479
480 toSave[p] = nlohmann::json( {} );
481 }
482 }
483
484 toSave.update( m_internals->begin(), m_internals->end(), /* merge_objects = */ true );
485
486 try
487 {
488 std::stringstream buffer;
489 buffer << std::setw( 2 ) << toSave << std::endl;
490
491 wxFFileOutputStream fileStream( path.GetFullPath(), "wb" );
492
493 if( !fileStream.IsOk()
494 || !fileStream.WriteAll( buffer.str().c_str(), buffer.str().size() ) )
495 {
496 wxLogTrace( traceSettings, wxT( "Warning: could not save %s" ), GetFullFilename() );
497 success = false;
498 }
499 }
500 catch( nlohmann::json::exception& error )
501 {
502 wxLogTrace( traceSettings, wxT( "Catch error: could not save %s. Json error %s" ),
503 GetFullFilename(), error.what() );
504 success = false;
505 }
506 catch( ... )
507 {
508 wxLogTrace( traceSettings, wxT( "Error: could not save %s." ) );
509 success = false;
510 }
511
512 if( success )
513 m_modified = false;
514
515 return success;
516}
517
518
520{
521 Store();
522
524
525 std::stringstream buffer;
526 buffer << std::setw( 2 ) << *m_internals << std::endl;
527
528 return buffer.str();
529}
530
531
532bool JSON_SETTINGS::LoadFromRawFile( const wxString& aPath )
533{
534 try
535 {
536 wxFFileInputStream fp( aPath, wxT( "rt" ) );
537 wxStdInputStream fstream( fp );
538
539 if( fp.IsOk() )
540 {
541 *static_cast<nlohmann::json*>( m_internals.get() ) =
542 nlohmann::json::parse( fstream, nullptr,
543 /* allow_exceptions = */ true,
544 /* ignore_comments = */ true );
545 }
546 else
547 {
548 return false;
549 }
550 }
551 catch( nlohmann::json::parse_error& error )
552 {
553 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
554
555 return false;
556 }
557
558 // Now that we have new data in the JSON structure, load the params again
559 Load();
560 return true;
561}
562
563
564std::optional<nlohmann::json> JSON_SETTINGS::GetJson( const std::string& aPath ) const
565{
566 nlohmann::json::json_pointer ptr = m_internals->PointerFromString( aPath );
567
568 if( m_internals->contains( ptr ) )
569 {
570 try
571 {
572 return std::optional<nlohmann::json>{ m_internals->at( ptr ) };
573 }
574 catch( ... )
575 {
576 }
577 }
578
579 return std::optional<nlohmann::json>{};
580}
581
582
583template<typename ValueType>
584std::optional<ValueType> JSON_SETTINGS::Get( const std::string& aPath ) const
585{
586 if( std::optional<nlohmann::json> ret = GetJson( aPath ) )
587 {
588 try
589 {
590 return ret->get<ValueType>();
591 }
592 catch( ... )
593 {
594 }
595 }
596
597 return std::nullopt;
598}
599
600
601// Instantiate all required templates here to allow reducing scope of json.hpp
602template KICOMMON_API std::optional<bool> JSON_SETTINGS::Get<bool>( const std::string& aPath ) const;
603template KICOMMON_API std::optional<double>
604 JSON_SETTINGS::Get<double>( const std::string& aPath ) const;
605template KICOMMON_API std::optional<float>
606 JSON_SETTINGS::Get<float>( const std::string& aPath ) const;
607template KICOMMON_API std::optional<int> JSON_SETTINGS::Get<int>( const std::string& aPath ) const;
608template KICOMMON_API std::optional<unsigned int>
609 JSON_SETTINGS::Get<unsigned int>( const std::string& aPath ) const;
610template KICOMMON_API std::optional<unsigned long long>
611 JSON_SETTINGS::Get<unsigned long long>( const std::string& aPath ) const;
612template KICOMMON_API std::optional<std::string>
613 JSON_SETTINGS::Get<std::string>( const std::string& aPath ) const;
614template KICOMMON_API std::optional<nlohmann::json>
615 JSON_SETTINGS::Get<nlohmann::json>( const std::string& aPath ) const;
616template KICOMMON_API std::optional<KIGFX::COLOR4D>
617 JSON_SETTINGS::Get<KIGFX::COLOR4D>( const std::string& aPath ) const;
618template KICOMMON_API std::optional<BOM_FIELD>
619 JSON_SETTINGS::Get<BOM_FIELD>( const std::string& aPath ) const;
620template KICOMMON_API std::optional<BOM_PRESET>
621 JSON_SETTINGS::Get<BOM_PRESET>( const std::string& aPath ) const;
622template KICOMMON_API std::optional<BOM_FMT_PRESET>
623 JSON_SETTINGS::Get<BOM_FMT_PRESET>( const std::string& aPath ) const;
624template KICOMMON_API std::optional<GRID> JSON_SETTINGS::Get<GRID>( const std::string& aPath ) const;
625template KICOMMON_API std::optional<wxPoint>
626 JSON_SETTINGS::Get<wxPoint>( const std::string& aPath ) const;
627template KICOMMON_API std::optional<wxSize>
628 JSON_SETTINGS::Get<wxSize>( const std::string& aPath ) const;
629template KICOMMON_API std::optional<wxRect>
630 JSON_SETTINGS::Get<wxRect>( const std::string& aPath ) const;
631template KICOMMON_API std::optional<wxAuiPaneInfo>
632 JSON_SETTINGS::Get<wxAuiPaneInfo>( const std::string& aPath ) const;
633
634template<typename ValueType>
635void JSON_SETTINGS::Set( const std::string& aPath, ValueType aVal )
636{
637 m_internals->SetFromString( aPath, std::move( aVal ) );
638}
639
640
641// Instantiate all required templates here to allow reducing scope of json.hpp
642template KICOMMON_API void JSON_SETTINGS::Set<bool>( const std::string& aPath, bool aValue );
643template KICOMMON_API void JSON_SETTINGS::Set<double>( const std::string& aPath, double aValue );
644template KICOMMON_API void JSON_SETTINGS::Set<float>( const std::string& aPath, float aValue );
645template KICOMMON_API void JSON_SETTINGS::Set<int>( const std::string& aPath, int aValue );
646template KICOMMON_API void JSON_SETTINGS::Set<unsigned int>( const std::string& aPath,
647 unsigned int aValue );
648template KICOMMON_API void JSON_SETTINGS::Set<unsigned long long>( const std::string& aPath,
649 unsigned long long aValue );
650template KICOMMON_API void JSON_SETTINGS::Set<const char*>( const std::string& aPath,
651 const char* aValue );
652template KICOMMON_API void JSON_SETTINGS::Set<std::string>( const std::string& aPath,
653 std::string aValue );
654template KICOMMON_API void JSON_SETTINGS::Set<nlohmann::json>( const std::string& aPath,
655 nlohmann::json aValue );
656template KICOMMON_API void JSON_SETTINGS::Set<KIGFX::COLOR4D>( const std::string& aPath,
657 KIGFX::COLOR4D aValue );
658template KICOMMON_API void JSON_SETTINGS::Set<BOM_FIELD>( const std::string& aPath,
659 BOM_FIELD aValue );
660template KICOMMON_API void JSON_SETTINGS::Set<BOM_PRESET>( const std::string& aPath,
661 BOM_PRESET aValue );
662template KICOMMON_API void JSON_SETTINGS::Set<BOM_FMT_PRESET>( const std::string& aPath,
663 BOM_FMT_PRESET aValue );
664template KICOMMON_API void JSON_SETTINGS::Set<GRID>( const std::string& aPath, GRID aValue );
665template KICOMMON_API void JSON_SETTINGS::Set<wxPoint>( const std::string& aPath, wxPoint aValue );
666template KICOMMON_API void JSON_SETTINGS::Set<wxSize>( const std::string& aPath, wxSize aValue );
667template KICOMMON_API void JSON_SETTINGS::Set<wxRect>( const std::string& aPath, wxRect aValue );
668template KICOMMON_API void JSON_SETTINGS::Set<wxAuiPaneInfo>( const std::string& aPath,
669 wxAuiPaneInfo aValue );
670
671
672void JSON_SETTINGS::registerMigration( int aOldSchemaVersion, int aNewSchemaVersion,
673 std::function<bool()> aMigrator )
674{
675 wxASSERT( aNewSchemaVersion > aOldSchemaVersion );
676 wxASSERT( aNewSchemaVersion <= m_schemaVersion );
677 m_migrators[aOldSchemaVersion] = std::make_pair( aNewSchemaVersion, aMigrator );
678}
679
680
682{
683 int filever = m_internals->Get<int>( "meta.version" );
684
685 while( filever < m_schemaVersion )
686 {
687 wxASSERT( m_migrators.count( filever ) > 0 );
688
689 if( !m_migrators.count( filever ) )
690 {
691 wxLogTrace( traceSettings, wxT( "Migrator missing for %s version %d!" ),
692 typeid( *this ).name(),
693 filever );
694 return false;
695 }
696
697 std::pair<int, std::function<bool()>> pair = m_migrators.at( filever );
698
699 if( pair.second() )
700 {
701 wxLogTrace( traceSettings, wxT( "Migrated %s from %d to %d" ),
702 typeid( *this ).name(),
703 filever,
704 pair.first );
705 filever = pair.first;
706 m_internals->At( "meta.version" ) = filever;
707 }
708 else
709 {
710 wxLogTrace( traceSettings, wxT( "Migration failed for %s from %d to %d" ),
711 typeid( *this ).name(),
712 filever,
713 pair.first );
714 return false;
715 }
716 }
717
718 return true;
719}
720
721
722bool JSON_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
723{
724 wxLogTrace( traceSettings, wxT( "MigrateFromLegacy() not implemented for %s" ),
725 typeid( *this ).name() );
726 return false;
727}
728
729
730bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
731 wxString& aTarget )
732{
733 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
734
735 if( aObj.contains( ptr ) && aObj.at( ptr ).is_string() )
736 {
737 aTarget = aObj.at( ptr ).get<wxString>();
738 return true;
739 }
740
741 return false;
742}
743
744
745bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
746 bool& aTarget )
747{
748 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
749
750 if( aObj.contains( ptr ) && aObj.at( ptr ).is_boolean() )
751 {
752 aTarget = aObj.at( ptr ).get<bool>();
753 return true;
754 }
755
756 return false;
757}
758
759
760bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
761 int& aTarget )
762{
763 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
764
765 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_integer() )
766 {
767 aTarget = aObj.at( ptr ).get<int>();
768 return true;
769 }
770
771 return false;
772}
773
774
775bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
776 unsigned int& aTarget )
777{
778 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
779
780 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_unsigned() )
781 {
782 aTarget = aObj.at( ptr ).get<unsigned int>();
783 return true;
784 }
785
786 return false;
787}
788
789
790template<typename ValueType>
791bool JSON_SETTINGS::fromLegacy( wxConfigBase* aConfig, const std::string& aKey,
792 const std::string& aDest )
793{
794 ValueType val;
795
796 if( aConfig->Read( aKey, &val ) )
797 {
798 try
799 {
800 ( *m_internals )[aDest] = val;
801 }
802 catch( ... )
803 {
804 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacy!" ) );
805 return false;
806 }
807
808 return true;
809 }
810
811 return false;
812}
813
814
815// Explicitly declare these because we only support a few types anyway, and it means we can keep
816// wxConfig detail out of the header file
817template
818KICOMMON_API bool JSON_SETTINGS::fromLegacy<int>( wxConfigBase*, const std::string&,
819 const std::string& );
820
821template
822KICOMMON_API bool JSON_SETTINGS::fromLegacy<double>( wxConfigBase*, const std::string&,
823 const std::string& );
824
825template
826KICOMMON_API bool JSON_SETTINGS::fromLegacy<bool>( wxConfigBase*, const std::string&,
827 const std::string& );
828
829
830bool JSON_SETTINGS::fromLegacyString( wxConfigBase* aConfig, const std::string& aKey,
831 const std::string& aDest )
832{
833 wxString str;
834
835 if( aConfig->Read( aKey, &str ) )
836 {
837 try
838 {
839 ( *m_internals )[aDest] = str.ToUTF8();
840 }
841 catch( ... )
842 {
843 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyString!" ) );
844 return false;
845 }
846
847 return true;
848 }
849
850 return false;
851}
852
853
854bool JSON_SETTINGS::fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
855 const std::string& aDest )
856{
857 wxString str;
858
859 if( aConfig->Read( aKey, &str ) )
860 {
862 color.SetFromWxString( str );
863
864 try
865 {
866 nlohmann::json js = nlohmann::json::array( { color.r, color.g, color.b, color.a } );
867 ( *m_internals )[aDest] = std::move( js );
868 }
869 catch( ... )
870 {
871 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyColor!" ) );
872 return false;
873 }
874
875 return true;
876 }
877
878 return false;
879}
880
881
883{
884 wxLogTrace( traceSettings, wxT( "AddNestedSettings %s" ), aSettings->GetFilename() );
885 m_nested_settings.push_back( aSettings );
886}
887
888
890{
891 if( !aSettings || !m_manager )
892 return;
893
894 auto it = std::find_if( m_nested_settings.begin(), m_nested_settings.end(),
895 [&aSettings]( const JSON_SETTINGS* aPtr )
896 {
897 return aPtr == aSettings;
898 } );
899
900 if( it != m_nested_settings.end() )
901 {
902 wxLogTrace( traceSettings, wxT( "Flush and release %s" ), ( *it )->GetFilename() );
903 m_modified |= ( *it )->SaveToFile();
904 m_nested_settings.erase( it );
905 }
906
907 aSettings->SetParent( nullptr );
908}
909
910
911// Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
912template<>
913std::optional<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const
914{
915 if( std::optional<nlohmann::json> opt_json = GetJson( aPath ) )
916 return wxString( opt_json->get<std::string>().c_str(), wxConvUTF8 );
917
918 return std::nullopt;
919}
920
921
922template<>
923void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal )
924{
925 ( *m_internals )[aPath] = aVal.ToUTF8();
926}
927
928
929template<typename ResultType>
930ResultType JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson, const std::string& aKey,
931 ResultType aDefault )
932{
933 ResultType ret = std::move( aDefault );
934
935 try
936 {
937 if( aJson.contains( aKey ) )
938 ret = aJson.at( aKey ).get<ResultType>();
939 }
940 catch( ... )
941 {
942 }
943
944 return ret;
945}
946
947
948template
949KICOMMON_API std::string JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
950 const std::string& aKey,
951 std::string aDefault );
952
953
954template
955KICOMMON_API bool JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
956 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)
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