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