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>
603 JSON_SETTINGS::Get<bool>( const std::string& aPath ) const;
604template KICOMMON_API std::optional<double>
605 JSON_SETTINGS::Get<double>( const std::string& aPath ) const;
606template KICOMMON_API std::optional<float>
607 JSON_SETTINGS::Get<float>( const std::string& aPath ) const;
608template KICOMMON_API std::optional<int> JSON_SETTINGS::Get<int>( const std::string& aPath ) const;
609template KICOMMON_API std::optional<unsigned int>
610 JSON_SETTINGS::Get<unsigned int>( const std::string& aPath ) const;
611template KICOMMON_API std::optional<unsigned long long>
612 JSON_SETTINGS::Get<unsigned long long>( const std::string& aPath ) const;
613template KICOMMON_API std::optional<std::string>
614 JSON_SETTINGS::Get<std::string>( const std::string& aPath ) const;
615template KICOMMON_API std::optional<nlohmann::json>
616 JSON_SETTINGS::Get<nlohmann::json>( const std::string& aPath ) const;
617template KICOMMON_API std::optional<KIGFX::COLOR4D>
618 JSON_SETTINGS::Get<KIGFX::COLOR4D>( const std::string& aPath ) const;
619template KICOMMON_API std::optional<BOM_FIELD>
620 JSON_SETTINGS::Get<BOM_FIELD>( const std::string& aPath ) const;
621template KICOMMON_API std::optional<BOM_PRESET>
622 JSON_SETTINGS::Get<BOM_PRESET>( const std::string& aPath ) const;
623template KICOMMON_API std::optional<BOM_FMT_PRESET>
624 JSON_SETTINGS::Get<BOM_FMT_PRESET>( const std::string& aPath ) const;
625template KICOMMON_API std::optional<GRID>
626 JSON_SETTINGS::Get<GRID>( const std::string& aPath ) const;
627template KICOMMON_API std::optional<wxPoint>
628 JSON_SETTINGS::Get<wxPoint>( const std::string& aPath ) const;
629template KICOMMON_API std::optional<wxSize>
630 JSON_SETTINGS::Get<wxSize>( const std::string& aPath ) const;
631template KICOMMON_API std::optional<wxRect>
632 JSON_SETTINGS::Get<wxRect>( const std::string& aPath ) const;
633template KICOMMON_API std::optional<wxAuiPaneInfo>
634 JSON_SETTINGS::Get<wxAuiPaneInfo>( const std::string& aPath ) const;
635
636template<typename ValueType>
637void JSON_SETTINGS::Set( const std::string& aPath, ValueType aVal )
638{
639 m_internals->SetFromString( aPath, std::move( aVal ) );
640}
641
642
643// Instantiate all required templates here to allow reducing scope of json.hpp
644template KICOMMON_API void JSON_SETTINGS::Set<bool>( const std::string& aPath, bool aValue );
645template KICOMMON_API void JSON_SETTINGS::Set<double>( const std::string& aPath, double aValue );
646template KICOMMON_API void JSON_SETTINGS::Set<float>( const std::string& aPath, float aValue );
647template KICOMMON_API void JSON_SETTINGS::Set<int>( const std::string& aPath, int aValue );
648template KICOMMON_API void JSON_SETTINGS::Set<unsigned int>( const std::string& aPath,
649 unsigned int aValue );
650template KICOMMON_API void JSON_SETTINGS::Set<unsigned long long>( const std::string& aPath,
651 unsigned long long aValue );
652template KICOMMON_API void JSON_SETTINGS::Set<const char*>( const std::string& aPath,
653 const char* aValue );
654template KICOMMON_API void JSON_SETTINGS::Set<std::string>( const std::string& aPath,
655 std::string aValue );
656template KICOMMON_API void JSON_SETTINGS::Set<nlohmann::json>( const std::string& aPath,
657 nlohmann::json aValue );
658template KICOMMON_API void JSON_SETTINGS::Set<KIGFX::COLOR4D>( const std::string& aPath,
659 KIGFX::COLOR4D aValue );
660template KICOMMON_API void JSON_SETTINGS::Set<BOM_FIELD>( const std::string& aPath,
661 BOM_FIELD aValue );
662template KICOMMON_API void JSON_SETTINGS::Set<BOM_PRESET>( const std::string& aPath,
663 BOM_PRESET aValue );
664template KICOMMON_API void JSON_SETTINGS::Set<BOM_FMT_PRESET>( const std::string& aPath,
665 BOM_FMT_PRESET aValue );
666template KICOMMON_API void JSON_SETTINGS::Set<GRID>( const std::string& aPath, GRID aValue );
667template KICOMMON_API void JSON_SETTINGS::Set<wxPoint>( const std::string& aPath, wxPoint aValue );
668template KICOMMON_API void JSON_SETTINGS::Set<wxSize>( const std::string& aPath, wxSize aValue );
669template KICOMMON_API void JSON_SETTINGS::Set<wxRect>( const std::string& aPath, wxRect aValue );
670template KICOMMON_API void JSON_SETTINGS::Set<wxAuiPaneInfo>( const std::string& aPath,
671 wxAuiPaneInfo aValue );
672
673
674void JSON_SETTINGS::registerMigration( int aOldSchemaVersion, int aNewSchemaVersion,
675 std::function<bool()> aMigrator )
676{
677 wxASSERT( aNewSchemaVersion > aOldSchemaVersion );
678 wxASSERT( aNewSchemaVersion <= m_schemaVersion );
679 m_migrators[aOldSchemaVersion] = std::make_pair( aNewSchemaVersion, aMigrator );
680}
681
682
684{
685 int filever = m_internals->Get<int>( "meta.version" );
686
687 while( filever < m_schemaVersion )
688 {
689 wxASSERT( m_migrators.count( filever ) > 0 );
690
691 if( !m_migrators.count( filever ) )
692 {
693 wxLogTrace( traceSettings, wxT( "Migrator missing for %s version %d!" ),
694 typeid( *this ).name(),
695 filever );
696 return false;
697 }
698
699 std::pair<int, std::function<bool()>> pair = m_migrators.at( filever );
700
701 if( pair.second() )
702 {
703 wxLogTrace( traceSettings, wxT( "Migrated %s from %d to %d" ),
704 typeid( *this ).name(),
705 filever,
706 pair.first );
707 filever = pair.first;
708 m_internals->At( "meta.version" ) = filever;
709 }
710 else
711 {
712 wxLogTrace( traceSettings, wxT( "Migration failed for %s from %d to %d" ),
713 typeid( *this ).name(),
714 filever,
715 pair.first );
716 return false;
717 }
718 }
719
720 return true;
721}
722
723
724bool JSON_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
725{
726 wxLogTrace( traceSettings, wxT( "MigrateFromLegacy() not implemented for %s" ),
727 typeid( *this ).name() );
728 return false;
729}
730
731
732bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
733 wxString& aTarget )
734{
735 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
736
737 if( aObj.contains( ptr ) && aObj.at( ptr ).is_string() )
738 {
739 aTarget = aObj.at( ptr ).get<wxString>();
740 return true;
741 }
742
743 return false;
744}
745
746
747bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
748 bool& aTarget )
749{
750 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
751
752 if( aObj.contains( ptr ) && aObj.at( ptr ).is_boolean() )
753 {
754 aTarget = aObj.at( ptr ).get<bool>();
755 return true;
756 }
757
758 return false;
759}
760
761
762bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
763 int& aTarget )
764{
765 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
766
767 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_integer() )
768 {
769 aTarget = aObj.at( ptr ).get<int>();
770 return true;
771 }
772
773 return false;
774}
775
776
777bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
778 unsigned int& aTarget )
779{
780 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
781
782 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_unsigned() )
783 {
784 aTarget = aObj.at( ptr ).get<unsigned int>();
785 return true;
786 }
787
788 return false;
789}
790
791
792template<typename ValueType>
793bool JSON_SETTINGS::fromLegacy( wxConfigBase* aConfig, const std::string& aKey,
794 const std::string& aDest )
795{
796 ValueType val;
797
798 if( aConfig->Read( aKey, &val ) )
799 {
800 try
801 {
802 ( *m_internals )[aDest] = val;
803 }
804 catch( ... )
805 {
806 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacy!" ) );
807 return false;
808 }
809
810 return true;
811 }
812
813 return false;
814}
815
816
817// Explicitly declare these because we only support a few types anyway, and it means we can keep
818// wxConfig detail out of the header file
819template
820KICOMMON_API bool JSON_SETTINGS::fromLegacy<int>( wxConfigBase*, const std::string&,
821 const std::string& );
822
823template
824KICOMMON_API bool JSON_SETTINGS::fromLegacy<double>( wxConfigBase*, const std::string&,
825 const std::string& );
826
827template
828KICOMMON_API bool JSON_SETTINGS::fromLegacy<bool>( wxConfigBase*, const std::string&,
829 const std::string& );
830
831
832bool JSON_SETTINGS::fromLegacyString( wxConfigBase* aConfig, const std::string& aKey,
833 const std::string& aDest )
834{
835 wxString str;
836
837 if( aConfig->Read( aKey, &str ) )
838 {
839 try
840 {
841 ( *m_internals )[aDest] = str.ToUTF8();
842 }
843 catch( ... )
844 {
845 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyString!" ) );
846 return false;
847 }
848
849 return true;
850 }
851
852 return false;
853}
854
855
856bool JSON_SETTINGS::fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
857 const std::string& aDest )
858{
859 wxString str;
860
861 if( aConfig->Read( aKey, &str ) )
862 {
864 color.SetFromWxString( str );
865
866 try
867 {
868 nlohmann::json js = nlohmann::json::array( { color.r, color.g, color.b, color.a } );
869 ( *m_internals )[aDest] = std::move( js );
870 }
871 catch( ... )
872 {
873 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyColor!" ) );
874 return false;
875 }
876
877 return true;
878 }
879
880 return false;
881}
882
883
885{
886 wxLogTrace( traceSettings, wxT( "AddNestedSettings %s" ), aSettings->GetFilename() );
887 m_nested_settings.push_back( aSettings );
888}
889
890
892{
893 if( !aSettings || !m_manager )
894 return;
895
896 auto it = std::find_if( m_nested_settings.begin(), m_nested_settings.end(),
897 [&aSettings]( const JSON_SETTINGS* aPtr )
898 {
899 return aPtr == aSettings;
900 } );
901
902 if( it != m_nested_settings.end() )
903 {
904 wxLogTrace( traceSettings, wxT( "Flush and release %s" ), ( *it )->GetFilename() );
905 m_modified |= ( *it )->SaveToFile();
906 m_nested_settings.erase( it );
907 }
908
909 aSettings->SetParent( nullptr );
910}
911
912
913// Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
914template<>
915std::optional<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const
916{
917 if( std::optional<nlohmann::json> opt_json = GetJson( aPath ) )
918 return wxString( opt_json->get<std::string>().c_str(), wxConvUTF8 );
919
920 return std::nullopt;
921}
922
923
924template<>
925void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal )
926{
927 ( *m_internals )[aPath] = aVal.ToUTF8();
928}
929
930
931template<typename ResultType>
932ResultType JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson, const std::string& aKey,
933 ResultType aDefault )
934{
935 ResultType ret = std::move( aDefault );
936
937 try
938 {
939 if( aJson.contains( aKey ) )
940 ret = aJson.at( aKey ).get<ResultType>();
941 }
942 catch( ... )
943 {
944 }
945
946 return ret;
947}
948
949
950template
951KICOMMON_API std::string JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
952 const std::string& aKey,
953 std::string aDefault );
954
955
956template
957KICOMMON_API bool JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
958 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