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