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 (C) 2020-2023 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
46const wxChar* const traceSettings = wxT( "KICAD_SETTINGS" );
47
48
49nlohmann::json::json_pointer JSON_SETTINGS_INTERNALS::PointerFromString( std::string aPath )
50{
51 std::replace( aPath.begin(), aPath.end(), '.', '/' );
52 aPath.insert( 0, "/" );
53
54 nlohmann::json::json_pointer p;
55
56 try
57 {
58 p = nlohmann::json::json_pointer( aPath );
59 }
60 catch( ... )
61 {
62 wxASSERT_MSG( false, wxT( "Invalid pointer path in PointerFromString!" ) );
63 }
64
65 return p;
66}
67
68
69JSON_SETTINGS::JSON_SETTINGS( const wxString& aFilename, SETTINGS_LOC aLocation,
70 int aSchemaVersion, bool aCreateIfMissing, bool aCreateIfDefault,
71 bool aWriteFile ) :
72 m_filename( aFilename ),
73 m_legacy_filename( "" ),
74 m_location( aLocation ),
75 m_createIfMissing( aCreateIfMissing ),
76 m_createIfDefault( aCreateIfDefault ),
77 m_writeFile( aWriteFile ),
78 m_deleteLegacyAfterMigration( true ),
79 m_resetParamsIfMissing( true ),
80 m_schemaVersion( aSchemaVersion ),
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(
97 new PARAM<int>( "meta.version", &m_schemaVersion, m_schemaVersion, 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.BeforeLast( '.' ) == getFileExt() )
113 return m_filename;
114
115 return wxString( m_filename + "." + getFileExt() );
116}
117
118
119nlohmann::json& JSON_SETTINGS::At( const std::string& aPath )
120{
121 return m_internals->At( aPath );
122}
123
124
125bool JSON_SETTINGS::Contains( const std::string& aPath ) const
126{
127 return m_internals->contains( JSON_SETTINGS_INTERNALS::PointerFromString( aPath ) );
128}
129
130
132{
133 return m_internals.get();
134}
135
136
138{
139 for( PARAM_BASE* param : m_params )
140 {
141 try
142 {
143 param->Load( this, m_resetParamsIfMissing );
144 }
145 catch( ... )
146 {
147 // Skip unreadable parameters in file
148 wxLogTrace( traceSettings, wxT( "param '%s' load err" ), param->GetJsonPath().c_str() );
149 }
150 }
151}
152
153
154bool JSON_SETTINGS::LoadFromFile( const wxString& aDirectory )
155{
156 // First, load all params to default values
157 m_internals->clear();
158 Load();
159
160 bool success = true;
161 bool migrated = false;
162 bool legacy_migrated = false;
163
164 LOCALE_IO locale;
165
166 auto migrateFromLegacy =
167 [&] ( wxFileName& aPath )
168 {
169 // Backup and restore during migration so that the original can be mutated if
170 // convenient
171 bool backed_up = false;
172 wxFileName temp;
173
174 if( aPath.IsDirWritable() )
175 {
176 temp.AssignTempFileName( aPath.GetFullPath() );
177
178 if( !wxCopyFile( aPath.GetFullPath(), temp.GetFullPath() ) )
179 {
180 wxLogTrace( traceSettings,
181 wxT( "%s: could not create temp file for migration" ),
182 GetFullFilename() );
183 }
184 else
185 {
186 backed_up = true;
187 }
188 }
189
190 // Silence popups if legacy file is read-only
191 wxLogNull doNotLog;
192
193 wxConfigBase::DontCreateOnDemand();
194 auto cfg = std::make_unique<wxFileConfig>( wxT( "" ), wxT( "" ),
195 aPath.GetFullPath() );
196
197 // If migrate fails or is not implemented, fall back to built-in defaults that
198 // were already loaded above
199 if( !MigrateFromLegacy( cfg.get() ) )
200 {
201 success = false;
202 wxLogTrace( traceSettings,
203 wxT( "%s: migrated; not all settings were found in legacy file" ),
204 GetFullFilename() );
205 }
206 else
207 {
208 success = true;
209 wxLogTrace( traceSettings, wxT( "%s: migrated from legacy format" ),
210 GetFullFilename() );
211 }
212
213 if( backed_up )
214 {
215 cfg.reset();
216
217 if( !wxCopyFile( temp.GetFullPath(), aPath.GetFullPath() ) )
218 {
219 wxLogTrace( traceSettings,
220 wxT( "migrate; copy temp file %s to %s failed" ),
221 temp.GetFullPath(),
222 aPath.GetFullPath() );
223 }
224
225 if( !wxRemoveFile( temp.GetFullPath() ) )
226 {
227 wxLogTrace( traceSettings,
228 wxT( "migrate; failed to remove temp file %s" ),
229 temp.GetFullPath() );
230 }
231 }
232
233 // Either way, we want to clean up the old file afterwards
234 legacy_migrated = true;
235 };
236
237 wxFileName path;
238
239 if( aDirectory.empty() )
240 {
241 path.Assign( m_filename );
242 path.SetExt( getFileExt() );
243 }
244 else
245 {
246 wxString dir( aDirectory );
247 path.Assign( dir, m_filename, getFileExt() );
248 }
249
250 if( !path.Exists() )
251 {
252 // Case 1: legacy migration, no .json extension yet
253 path.SetExt( getLegacyFileExt() );
254
255 if( path.Exists() )
256 {
257 migrateFromLegacy( path );
258 }
259 // Case 2: legacy filename is different from new one
260 else if( !m_legacy_filename.empty() )
261 {
262 path.SetName( m_legacy_filename );
263
264 if( path.Exists() )
265 migrateFromLegacy( path );
266 }
267 else
268 {
269 success = false;
270 }
271 }
272 else
273 {
274 if( !path.IsFileWritable() )
275 m_writeFile = false;
276
277 try
278 {
279 wxFFileInputStream fp( path.GetFullPath(), wxT( "rt" ) );
280 wxStdInputStream fstream( fp );
281
282 if( fp.IsOk() )
283 {
284 *static_cast<nlohmann::json*>( m_internals.get() ) =
285 nlohmann::json::parse( fstream, nullptr,
286 /* allow_exceptions = */ true,
287 /* ignore_comments = */ true );
288
289 // Save whatever we loaded, before doing any migration etc
290 m_internals->m_original = *static_cast<nlohmann::json*>( m_internals.get() );
291
292 // If parse succeeds, check if schema migration is required
293 int filever = -1;
294
295 try
296 {
297 filever = m_internals->Get<int>( "meta.version" );
298 }
299 catch( ... )
300 {
301 wxLogTrace( traceSettings, wxT( "%s: file version could not be read!" ),
302 GetFullFilename() );
303 success = false;
304 }
305
306 if( filever >= 0 && filever < m_schemaVersion )
307 {
308 wxLogTrace( traceSettings, wxT( "%s: attempting migration from version "
309 "%d to %d" ),
310 GetFullFilename(), filever, m_schemaVersion );
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)" ),
326 GetFullFilename(), filever, m_schemaVersion );
327 }
328 }
329 else
330 {
331 wxLogTrace( traceSettings, wxT( "%s exists but can't be opened for read" ),
332 GetFullFilename() );
333 }
334 }
335 catch( nlohmann::json::parse_error& error )
336 {
337 success = false;
338 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ),
339 path.GetFullPath(), error.what() );
340 wxLogTrace( traceSettings, wxT( "Attempting migration in case file is in legacy "
341 "format" ) );
342 migrateFromLegacy( path );
343 }
344 }
345
346 // Now that we have new data in the JSON structure, load the params again
347 Load();
348
349 // And finally load any nested settings
350 for( NESTED_SETTINGS* settings : m_nested_settings )
351 settings->LoadFromFile();
352
353 wxLogTrace( traceSettings, wxT( "Loaded <%s> with schema %d" ), GetFullFilename(),
355
356 // If we migrated, clean up the legacy file (with no extension)
357 if( m_writeFile && ( legacy_migrated || migrated ) )
358 {
359 if( legacy_migrated && m_deleteLegacyAfterMigration && !wxRemoveFile( path.GetFullPath() ) )
360 {
361 wxLogTrace( traceSettings, wxT( "Warning: could not remove legacy file %s" ),
362 path.GetFullPath() );
363 }
364
365 // And write-out immediately so that we don't lose data if the program later crashes.
367 SaveToFile( aDirectory, true );
368 }
369
370 return success;
371}
372
373
375{
376 bool modified = false;
377
378 for( PARAM_BASE* param : m_params )
379 {
380 modified |= !param->MatchesFile( this );
381 param->Store( this );
382 }
383
384 return modified;
385}
386
387
389{
390 for( PARAM_BASE* param : m_params )
391 param->SetDefault();
392}
393
394
395bool JSON_SETTINGS::SaveToFile( const wxString& aDirectory, bool aForce )
396{
397 if( !m_writeFile )
398 return false;
399
400 // Default PROJECT won't have a filename set
401 if( m_filename.IsEmpty() )
402 return false;
403
404 wxFileName path;
405
406 if( aDirectory.empty() )
407 {
408 path.Assign( m_filename );
409 path.SetExt( getFileExt() );
410 }
411 else
412 {
413 wxString dir( aDirectory );
414 path.Assign( dir, m_filename, getFileExt() );
415 }
416
417 if( !m_createIfMissing && !path.FileExists() )
418 {
419 wxLogTrace( traceSettings,
420 wxT( "File for %s doesn't exist and m_createIfMissing == false; not saving" ),
421 GetFullFilename() );
422 return false;
423 }
424
425 // Ensure the path exists, and create it if not.
426 if( !path.DirExists() && !path.Mkdir() )
427 {
428 wxLogTrace( traceSettings, wxT( "Warning: could not create path %s, can't save %s" ),
429 path.GetPath(), GetFullFilename() );
430 return false;
431 }
432
433 if( ( path.FileExists() && !path.IsFileWritable() ) ||
434 ( !path.FileExists() && !path.IsDirWritable() ) )
435 {
436 wxLogTrace( traceSettings, wxT( "File for %s is read-only; not saving" ),
437 GetFullFilename() );
438 return false;
439 }
440
441 bool modified = false;
442
443 for( NESTED_SETTINGS* settings : m_nested_settings )
444 modified |= settings->SaveToFile();
445
446 modified |= Store();
447
448 if( !modified && !aForce && path.FileExists() )
449 {
450 wxLogTrace( traceSettings, wxT( "%s contents not modified, skipping save" ),
451 GetFullFilename() );
452 return false;
453 }
454 else if( !modified && !aForce && !m_createIfDefault )
455 {
456 wxLogTrace( traceSettings,
457 wxT( "%s contents still default and m_createIfDefault == false; not saving" ),
458 GetFullFilename() );
459 return false;
460 }
461
462 wxLogTrace( traceSettings, wxT( "Saving %s" ), GetFullFilename() );
463
465 bool success = true;
466
467 nlohmann::json toSave = m_internals->m_original;
468
469
470 for( PARAM_BASE* param : m_params )
471 {
472 if( PARAM_WXSTRING_MAP* stringMap = dynamic_cast<PARAM_WXSTRING_MAP*>( param ) )
473 {
474 if( stringMap->ClearUnknownKeys() )
475 toSave[ stringMap->GetJsonPath() ] = nlohmann::json( {} );
476 }
477 }
478
479 toSave.update( m_internals->begin(), m_internals->end(), /* merge_objects = */ true );
480
481 try
482 {
483 std::stringstream buffer;
484 buffer << std::setw( 2 ) << toSave << std::endl;
485
486 wxFFileOutputStream fileStream( path.GetFullPath(), "wb" );
487
488 if( !fileStream.IsOk()
489 || !fileStream.WriteAll( buffer.str().c_str(), buffer.str().size() ) )
490 {
491 wxLogTrace( traceSettings, wxT( "Warning: could not save %s" ), GetFullFilename() );
492 success = false;
493 }
494 }
495 catch( nlohmann::json::exception& error )
496 {
497 wxLogTrace( traceSettings, wxT( "Catch error: could not save %s. Json error %s" ),
498 GetFullFilename(), error.what() );
499 success = false;
500 }
501 catch( ... )
502 {
503 wxLogTrace( traceSettings, wxT( "Error: could not save %s." ) );
504 success = false;
505 }
506
507 return success;
508}
509
510
512{
513 Store();
514
516
517 std::stringstream buffer;
518 buffer << std::setw( 2 ) << *m_internals << std::endl;
519
520 return buffer.str();
521}
522
523
524bool JSON_SETTINGS::LoadFromRawFile( const wxString& aPath )
525{
526 try
527 {
528 wxFFileInputStream fp( aPath, wxT( "rt" ) );
529 wxStdInputStream fstream( fp );
530
531 if( fp.IsOk() )
532 {
533 *static_cast<nlohmann::json*>( m_internals.get() ) =
534 nlohmann::json::parse( fstream, nullptr,
535 /* allow_exceptions = */ true,
536 /* ignore_comments = */ true );
537 }
538 else
539 {
540 return false;
541 }
542 }
543 catch( nlohmann::json::parse_error& error )
544 {
545 wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
546
547 return false;
548 }
549
550 // Now that we have new data in the JSON structure, load the params again
551 Load();
552 return true;
553}
554
555
556std::optional<nlohmann::json> JSON_SETTINGS::GetJson( const std::string& aPath ) const
557{
558 nlohmann::json::json_pointer ptr = m_internals->PointerFromString( aPath );
559
560 if( m_internals->contains( ptr ) )
561 {
562 try
563 {
564 return std::optional<nlohmann::json>{ m_internals->at( ptr ) };
565 }
566 catch( ... )
567 {
568 }
569 }
570
571 return std::optional<nlohmann::json>{};
572}
573
574
575template<typename ValueType>
576std::optional<ValueType> JSON_SETTINGS::Get( const std::string& aPath ) const
577{
578 if( std::optional<nlohmann::json> ret = GetJson( aPath ) )
579 {
580 try
581 {
582 return ret->get<ValueType>();
583 }
584 catch( ... )
585 {
586 }
587 }
588
589 return std::nullopt;
590}
591
592
593// Instantiate all required templates here to allow reducing scope of json.hpp
594template std::optional<bool> JSON_SETTINGS::Get<bool>( const std::string& aPath ) const;
595template std::optional<double> JSON_SETTINGS::Get<double>( const std::string& aPath ) const;
596template std::optional<float> JSON_SETTINGS::Get<float>( const std::string& aPath ) const;
597template std::optional<int> JSON_SETTINGS::Get<int>( const std::string& aPath ) const;
598template std::optional<unsigned int> JSON_SETTINGS::Get<unsigned int>( const std::string& aPath ) const;
599template std::optional<unsigned long long> JSON_SETTINGS::Get<unsigned long long>( const std::string& aPath ) const;
600template std::optional<std::string> JSON_SETTINGS::Get<std::string>( const std::string& aPath ) const;
601template std::optional<nlohmann::json> JSON_SETTINGS::Get<nlohmann::json>( const std::string& aPath ) const;
602template std::optional<KIGFX::COLOR4D> JSON_SETTINGS::Get<KIGFX::COLOR4D>( const std::string& aPath ) const;
603template std::optional<BOM_FIELD> JSON_SETTINGS::Get<BOM_FIELD>( const std::string& aPath ) const;
604template std::optional<BOM_PRESET> JSON_SETTINGS::Get<BOM_PRESET>( const std::string& aPath ) const;
605template std::optional<BOM_FMT_PRESET> JSON_SETTINGS::Get<BOM_FMT_PRESET>( const std::string& aPath ) const;
606template std::optional<GRID> JSON_SETTINGS::Get<GRID>( const std::string& aPath ) const;
607template std::optional<wxPoint> JSON_SETTINGS::Get<wxPoint>( const std::string& aPath ) const;
608template std::optional<wxSize> JSON_SETTINGS::Get<wxSize>( const std::string& aPath ) const;
609template std::optional<wxRect> JSON_SETTINGS::Get<wxRect>( const std::string& aPath ) const;
610template std::optional<wxAuiPaneInfo> JSON_SETTINGS::Get<wxAuiPaneInfo>( const std::string& aPath ) const;
611
612template<typename ValueType>
613void JSON_SETTINGS::Set( const std::string& aPath, ValueType aVal )
614{
615 m_internals->SetFromString( aPath, std::move( aVal ) );
616}
617
618
619// Instantiate all required templates here to allow reducing scope of json.hpp
620template void JSON_SETTINGS::Set<bool>( const std::string& aPath, bool aValue );
621template void JSON_SETTINGS::Set<double>( const std::string& aPath, double aValue );
622template void JSON_SETTINGS::Set<float>( const std::string& aPath, float aValue );
623template void JSON_SETTINGS::Set<int>( const std::string& aPath, int aValue );
624template void JSON_SETTINGS::Set<unsigned int>( const std::string& aPath, unsigned int aValue );
625template void JSON_SETTINGS::Set<unsigned long long>( const std::string& aPath, unsigned long long aValue );
626template void JSON_SETTINGS::Set<const char*>( const std::string& aPath, const char* aValue );
627template void JSON_SETTINGS::Set<std::string>( const std::string& aPath, std::string aValue );
628template void JSON_SETTINGS::Set<nlohmann::json>( const std::string& aPath, nlohmann::json aValue );
629template void JSON_SETTINGS::Set<KIGFX::COLOR4D>( const std::string& aPath, KIGFX::COLOR4D aValue );
630template void JSON_SETTINGS::Set<BOM_FIELD>( const std::string& aPath, BOM_FIELD aValue );
631template void JSON_SETTINGS::Set<BOM_PRESET>( const std::string& aPath, BOM_PRESET aValue );
632template void JSON_SETTINGS::Set<BOM_FMT_PRESET>( const std::string& aPath, BOM_FMT_PRESET aValue );
633template void JSON_SETTINGS::Set<GRID>( const std::string& aPath, GRID aValue );
634template void JSON_SETTINGS::Set<wxPoint>( const std::string& aPath, wxPoint aValue );
635template void JSON_SETTINGS::Set<wxSize>( const std::string& aPath, wxSize aValue );
636template void JSON_SETTINGS::Set<wxRect>( const std::string& aPath, wxRect aValue );
637template void JSON_SETTINGS::Set<wxAuiPaneInfo>( const std::string& aPath, wxAuiPaneInfo aValue );
638
639
640void JSON_SETTINGS::registerMigration( int aOldSchemaVersion, int aNewSchemaVersion,
641 std::function<bool()> aMigrator )
642{
643 wxASSERT( aNewSchemaVersion > aOldSchemaVersion );
644 wxASSERT( aNewSchemaVersion <= m_schemaVersion );
645 m_migrators[aOldSchemaVersion] = std::make_pair( aNewSchemaVersion, aMigrator );
646}
647
648
650{
651 int filever = m_internals->Get<int>( "meta.version" );
652
653 while( filever < m_schemaVersion )
654 {
655 if( !m_migrators.count( filever ) )
656 {
657 wxLogTrace( traceSettings, wxT( "Migrator missing for %s version %d!" ),
658 typeid( *this ).name(), filever );
659 return false;
660 }
661
662 std::pair<int, std::function<bool()>> pair = m_migrators.at( filever );
663
664 if( pair.second() )
665 {
666 wxLogTrace( traceSettings, wxT( "Migrated %s from %d to %d" ), typeid( *this ).name(),
667 filever, pair.first );
668 filever = pair.first;
669 m_internals->At( "meta.version" ) = filever;
670 }
671 else
672 {
673 wxLogTrace( traceSettings, wxT( "Migration failed for %s from %d to %d" ),
674 typeid( *this ).name(), filever, pair.first );
675 return false;
676 }
677 }
678
679 return true;
680}
681
682
683bool JSON_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
684{
685 wxLogTrace( traceSettings,
686 wxT( "MigrateFromLegacy() not implemented for %s" ), typeid( *this ).name() );
687 return false;
688}
689
690
691bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
692 wxString& aTarget )
693{
694 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
695
696 if( aObj.contains( ptr ) && aObj.at( ptr ).is_string() )
697 {
698 aTarget = aObj.at( ptr ).get<wxString>();
699 return true;
700 }
701
702 return false;
703}
704
705
706bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
707 bool& aTarget )
708{
709 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
710
711 if( aObj.contains( ptr ) && aObj.at( ptr ).is_boolean() )
712 {
713 aTarget = aObj.at( ptr ).get<bool>();
714 return true;
715 }
716
717 return false;
718}
719
720
721bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
722 int& aTarget )
723{
724 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
725
726 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_integer() )
727 {
728 aTarget = aObj.at( ptr ).get<int>();
729 return true;
730 }
731
732 return false;
733}
734
735
736bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
737 unsigned int& aTarget )
738{
739 nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
740
741 if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_unsigned() )
742 {
743 aTarget = aObj.at( ptr ).get<unsigned int>();
744 return true;
745 }
746
747 return false;
748}
749
750
751template<typename ValueType>
752bool JSON_SETTINGS::fromLegacy( wxConfigBase* aConfig, const std::string& aKey,
753 const std::string& aDest )
754{
755 ValueType val;
756
757 if( aConfig->Read( aKey, &val ) )
758 {
759 try
760 {
761 ( *m_internals )[aDest] = val;
762 }
763 catch( ... )
764 {
765 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacy!" ) );
766 return false;
767 }
768
769 return true;
770 }
771
772 return false;
773}
774
775
776// Explicitly declare these because we only support a few types anyway, and it means we can keep
777// wxConfig detail out of the header file
778template bool JSON_SETTINGS::fromLegacy<int>( wxConfigBase*, const std::string&,
779 const std::string& );
780
781template bool JSON_SETTINGS::fromLegacy<double>( wxConfigBase*, const std::string&,
782 const std::string& );
783
784template bool JSON_SETTINGS::fromLegacy<bool>( wxConfigBase*, const std::string&,
785 const std::string& );
786
787
788bool JSON_SETTINGS::fromLegacyString( wxConfigBase* aConfig, const std::string& aKey,
789 const std::string& aDest )
790{
791 wxString str;
792
793 if( aConfig->Read( aKey, &str ) )
794 {
795 try
796 {
797 ( *m_internals )[aDest] = str.ToUTF8();
798 }
799 catch( ... )
800 {
801 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyString!" ) );
802 return false;
803 }
804
805 return true;
806 }
807
808 return false;
809}
810
811
812bool JSON_SETTINGS::fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
813 const std::string& aDest )
814{
815 wxString str;
816
817 if( aConfig->Read( aKey, &str ) )
818 {
820 color.SetFromWxString( str );
821
822 try
823 {
824 nlohmann::json js = nlohmann::json::array( { color.r, color.g, color.b, color.a } );
825 ( *m_internals )[aDest] = std::move( js );
826 }
827 catch( ... )
828 {
829 wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyColor!" ) );
830 return false;
831 }
832
833 return true;
834 }
835
836 return false;
837}
838
839
841{
842 wxLogTrace( traceSettings, wxT( "AddNestedSettings %s" ), aSettings->GetFilename() );
843 m_nested_settings.push_back( aSettings );
844}
845
846
848{
849 if( !aSettings || !m_manager )
850 return;
851
852 auto it = std::find_if( m_nested_settings.begin(), m_nested_settings.end(),
853 [&aSettings]( const JSON_SETTINGS* aPtr )
854 {
855 return aPtr == aSettings;
856 } );
857
858 if( it != m_nested_settings.end() )
859 {
860 wxLogTrace( traceSettings, wxT( "Flush and release %s" ), ( *it )->GetFilename() );
861 ( *it )->SaveToFile();
862 m_nested_settings.erase( it );
863 }
864
865 aSettings->SetParent( nullptr );
866}
867
868
869// Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
870template<> std::optional<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const
871{
872 if( std::optional<nlohmann::json> opt_json = GetJson( aPath ) )
873 return wxString( opt_json->get<std::string>().c_str(), wxConvUTF8 );
874
875 return std::nullopt;
876}
877
878
879template<> void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal )
880{
881 ( *m_internals )[aPath] = aVal.ToUTF8();
882}
883
884
885// Specializations to allow directly reading/writing wxStrings from JSON
886void to_json( nlohmann::json& aJson, const wxString& aString )
887{
888 aJson = aString.ToUTF8();
889}
890
891
892void from_json( const nlohmann::json& aJson, wxString& aString )
893{
894 aString = wxString( aJson.get<std::string>().c_str(), wxConvUTF8 );
895}
896
897
898template<typename ResultType>
899ResultType JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson, const std::string& aKey,
900 ResultType aDefault )
901{
902 ResultType ret = std::move( aDefault );
903
904 try
905 {
906 if( aJson.contains( aKey ) )
907 ret = aJson.at( aKey ).get<ResultType>();
908 }
909 catch( ... )
910 {
911 }
912
913 return ret;
914}
915
916
917template std::string JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
918 const std::string& aKey, std::string aDefault );
919
920
921template bool JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson, const std::string& aKey,
922 bool aDefault );
int color
Definition: DXF_plotter.cpp:58
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.
virtual wxString getFileExt() const
std::optional< ValueType > Get(const std::string &aPath) const
Fetches a value from within the JSON document.
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....
bool fromLegacy(wxConfigBase *aConfig, const std::string &aKey, const std::string &aDest)
Translates a legacy wxConfig value to a given JSON pointer value.
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.
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::vector< PARAM_BASE * > m_params
The list of parameters (owned by this object)
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.
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()
void ReleaseNestedSettings(NESTED_SETTINGS *aSettings)
Saves and frees a nested settings object, if it exists within this one.
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...
JSON_SETTINGS(const wxString &aFilename, SETTINGS_LOC aLocation, int aSchemaVersion)
Definition: json_settings.h:64
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:73
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)
A helper for <wxString, wxString> maps.
Definition: parameters.h:584
const wxChar *const traceSettings
Flag to enable debug output of settings operations and management.
void from_json(const nlohmann::json &aJson, wxString &aString)
void to_json(nlohmann::json &aJson, const wxString &aString)
SETTINGS_LOC
Definition: json_settings.h:47
std::vector< FAB_LAYER_COLOR > dummy
Common grid settings, available to every frame.
Definition: grid_settings.h:34