You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

864 lines
25 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2020 Jon Evans <jon@craftyjon.com>
  5. * Copyright (C) 2020-2022 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. #include <algorithm>
  21. #include <fstream>
  22. #include <iomanip>
  23. #include <utility>
  24. #include <sstream>
  25. #include <locale_io.h>
  26. #include <gal/color4d.h>
  27. #include <settings/json_settings.h>
  28. #include <settings/json_settings_internals.h>
  29. #include <settings/nested_settings.h>
  30. #include <settings/parameters.h>
  31. #include <wx/config.h>
  32. #include <wx/debug.h>
  33. #include <wx/fileconf.h>
  34. #include <wx/filename.h>
  35. #include <wx/log.h>
  36. #include <wx/stdstream.h>
  37. #include <wx/wfstream.h>
  38. const wxChar* const traceSettings = wxT( "KICAD_SETTINGS" );
  39. nlohmann::json::json_pointer JSON_SETTINGS_INTERNALS::PointerFromString( std::string aPath )
  40. {
  41. std::replace( aPath.begin(), aPath.end(), '.', '/' );
  42. aPath.insert( 0, "/" );
  43. nlohmann::json::json_pointer p;
  44. try
  45. {
  46. p = nlohmann::json::json_pointer( aPath );
  47. }
  48. catch( ... )
  49. {
  50. wxASSERT_MSG( false, wxT( "Invalid pointer path in PointerFromString!" ) );
  51. }
  52. return p;
  53. }
  54. JSON_SETTINGS::JSON_SETTINGS( const wxString& aFilename, SETTINGS_LOC aLocation,
  55. int aSchemaVersion, bool aCreateIfMissing, bool aCreateIfDefault,
  56. bool aWriteFile ) :
  57. m_filename( aFilename ),
  58. m_legacy_filename( "" ),
  59. m_location( aLocation ),
  60. m_createIfMissing( aCreateIfMissing ),
  61. m_createIfDefault( aCreateIfDefault ),
  62. m_writeFile( aWriteFile ),
  63. m_deleteLegacyAfterMigration( true ),
  64. m_resetParamsIfMissing( true ),
  65. m_schemaVersion( aSchemaVersion ),
  66. m_manager( nullptr )
  67. {
  68. m_internals = std::make_unique<JSON_SETTINGS_INTERNALS>();
  69. try
  70. {
  71. m_internals->SetFromString( "meta.filename", GetFullFilename() );
  72. }
  73. catch( ... )
  74. {
  75. wxLogTrace( traceSettings, wxT( "Error: Could not create filename field for %s" ),
  76. GetFullFilename() );
  77. }
  78. m_params.emplace_back(
  79. new PARAM<int>( "meta.version", &m_schemaVersion, m_schemaVersion, true ) );
  80. }
  81. JSON_SETTINGS::~JSON_SETTINGS()
  82. {
  83. for( auto param: m_params )
  84. delete param;
  85. m_params.clear();
  86. }
  87. wxString JSON_SETTINGS::GetFullFilename() const
  88. {
  89. return wxString( m_filename + "." + getFileExt() );
  90. }
  91. nlohmann::json& JSON_SETTINGS::At( const std::string& aPath )
  92. {
  93. return m_internals->At( aPath );
  94. }
  95. bool JSON_SETTINGS::Contains( const std::string& aPath ) const
  96. {
  97. return m_internals->contains( JSON_SETTINGS_INTERNALS::PointerFromString( aPath ) );
  98. }
  99. JSON_SETTINGS_INTERNALS* JSON_SETTINGS::Internals()
  100. {
  101. return m_internals.get();
  102. }
  103. void JSON_SETTINGS::Load()
  104. {
  105. for( auto param : m_params )
  106. {
  107. try
  108. {
  109. param->Load( this, m_resetParamsIfMissing );
  110. }
  111. catch( ... )
  112. {
  113. // Skip unreadable parameters in file
  114. wxLogTrace( traceSettings, wxT( "param '%s' load err" ), param->GetJsonPath().c_str() );
  115. }
  116. }
  117. }
  118. bool JSON_SETTINGS::LoadFromFile( const wxString& aDirectory )
  119. {
  120. // First, load all params to default values
  121. m_internals->clear();
  122. Load();
  123. bool success = true;
  124. bool migrated = false;
  125. bool legacy_migrated = false;
  126. LOCALE_IO locale;
  127. auto migrateFromLegacy = [&] ( wxFileName& aPath ) {
  128. // Backup and restore during migration so that the original can be mutated if convenient
  129. bool backed_up = false;
  130. wxFileName temp;
  131. if( aPath.IsDirWritable() )
  132. {
  133. temp.AssignTempFileName( aPath.GetFullPath() );
  134. if( !wxCopyFile( aPath.GetFullPath(), temp.GetFullPath() ) )
  135. {
  136. wxLogTrace( traceSettings, wxT( "%s: could not create temp file for migration" ),
  137. GetFullFilename() );
  138. }
  139. else
  140. backed_up = true;
  141. }
  142. // Silence popups if legacy file is read-only
  143. wxLogNull doNotLog;
  144. wxConfigBase::DontCreateOnDemand();
  145. auto cfg = std::make_unique<wxFileConfig>( wxT( "" ), wxT( "" ), aPath.GetFullPath() );
  146. // If migrate fails or is not implemented, fall back to built-in defaults that were
  147. // already loaded above
  148. if( !MigrateFromLegacy( cfg.get() ) )
  149. {
  150. success = false;
  151. wxLogTrace( traceSettings,
  152. wxT( "%s: migrated; not all settings were found in legacy file" ),
  153. GetFullFilename() );
  154. }
  155. else
  156. {
  157. success = true;
  158. wxLogTrace( traceSettings, wxT( "%s: migrated from legacy format" ), GetFullFilename() );
  159. }
  160. if( backed_up )
  161. {
  162. cfg.reset();
  163. if( !wxCopyFile( temp.GetFullPath(), aPath.GetFullPath() ) )
  164. {
  165. wxLogTrace( traceSettings,
  166. wxT( "migrate; copy temp file %s to %s failed" ),
  167. temp.GetFullPath(), aPath.GetFullPath() );
  168. }
  169. if( !wxRemoveFile( temp.GetFullPath() ) )
  170. {
  171. wxLogTrace( traceSettings,
  172. wxT( "migrate; failed to remove temp file %s" ),
  173. temp.GetFullPath() );
  174. }
  175. }
  176. // Either way, we want to clean up the old file afterwards
  177. legacy_migrated = true;
  178. };
  179. wxFileName path;
  180. if( aDirectory.empty() )
  181. {
  182. path.Assign( m_filename );
  183. path.SetExt( getFileExt() );
  184. }
  185. else
  186. {
  187. wxString dir( aDirectory );
  188. path.Assign( dir, m_filename, getFileExt() );
  189. }
  190. if( !path.Exists() )
  191. {
  192. // Case 1: legacy migration, no .json extension yet
  193. path.SetExt( getLegacyFileExt() );
  194. if( path.Exists() )
  195. {
  196. migrateFromLegacy( path );
  197. }
  198. // Case 2: legacy filename is different from new one
  199. else if( !m_legacy_filename.empty() )
  200. {
  201. path.SetName( m_legacy_filename );
  202. if( path.Exists() )
  203. migrateFromLegacy( path );
  204. }
  205. else
  206. {
  207. success = false;
  208. }
  209. }
  210. else
  211. {
  212. if( !path.IsFileWritable() )
  213. m_writeFile = false;
  214. try
  215. {
  216. wxFFileInputStream fp( path.GetFullPath(), wxT( "rt" ) );
  217. wxStdInputStream fstream( fp );
  218. if( fp.IsOk() )
  219. {
  220. *static_cast<nlohmann::json*>( m_internals.get() ) =
  221. nlohmann::json::parse( fstream, nullptr,
  222. /* allow_exceptions = */ true,
  223. /* ignore_comments = */ true );
  224. // If parse succeeds, check if schema migration is required
  225. int filever = -1;
  226. try
  227. {
  228. filever = m_internals->Get<int>( "meta.version" );
  229. }
  230. catch( ... )
  231. {
  232. wxLogTrace( traceSettings, wxT( "%s: file version could not be read!" ),
  233. GetFullFilename() );
  234. success = false;
  235. }
  236. if( filever >= 0 && filever < m_schemaVersion )
  237. {
  238. wxLogTrace( traceSettings, wxT( "%s: attempting migration from version %d to %d" ),
  239. GetFullFilename(), filever, m_schemaVersion );
  240. if( Migrate() )
  241. {
  242. migrated = true;
  243. }
  244. else
  245. {
  246. wxLogTrace( traceSettings, wxT( "%s: migration failed!" ), GetFullFilename() );
  247. }
  248. }
  249. else if( filever > m_schemaVersion )
  250. {
  251. wxLogTrace( traceSettings,
  252. wxT( "%s: warning: file version %d is newer than latest (%d)" ),
  253. GetFullFilename(), filever, m_schemaVersion );
  254. }
  255. }
  256. else
  257. {
  258. wxLogTrace( traceSettings, wxT( "%s exists but can't be opened for read" ),
  259. GetFullFilename() );
  260. }
  261. }
  262. catch( nlohmann::json::parse_error& error )
  263. {
  264. success = false;
  265. wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ),
  266. path.GetFullPath(), error.what() );
  267. wxLogTrace( traceSettings, wxT( "Attempting migration in case file is in legacy format" ) );
  268. migrateFromLegacy( path );
  269. }
  270. }
  271. // Now that we have new data in the JSON structure, load the params again
  272. Load();
  273. // And finally load any nested settings
  274. for( auto settings : m_nested_settings )
  275. settings->LoadFromFile();
  276. wxLogTrace( traceSettings, wxT( "Loaded <%s> with schema %d" ), GetFullFilename(), m_schemaVersion );
  277. // If we migrated, clean up the legacy file (with no extension)
  278. if( m_writeFile && ( legacy_migrated || migrated ) )
  279. {
  280. if( legacy_migrated && m_deleteLegacyAfterMigration && !wxRemoveFile( path.GetFullPath() ) )
  281. {
  282. wxLogTrace( traceSettings, wxT( "Warning: could not remove legacy file %s" ),
  283. path.GetFullPath() );
  284. }
  285. // And write-out immediately so that we don't lose data if the program later crashes.
  286. if( m_deleteLegacyAfterMigration )
  287. SaveToFile( aDirectory, true );
  288. }
  289. return success;
  290. }
  291. bool JSON_SETTINGS::Store()
  292. {
  293. bool modified = false;
  294. for( auto param : m_params )
  295. {
  296. modified |= !param->MatchesFile( this );
  297. param->Store( this );
  298. }
  299. return modified;
  300. }
  301. void JSON_SETTINGS::ResetToDefaults()
  302. {
  303. for( auto param : m_params )
  304. param->SetDefault();
  305. }
  306. bool JSON_SETTINGS::SaveToFile( const wxString& aDirectory, bool aForce )
  307. {
  308. if( !m_writeFile )
  309. return false;
  310. // Default PROJECT won't have a filename set
  311. if( m_filename.IsEmpty() )
  312. return false;
  313. wxFileName path;
  314. if( aDirectory.empty() )
  315. {
  316. path.Assign( m_filename );
  317. path.SetExt( getFileExt() );
  318. }
  319. else
  320. {
  321. wxString dir( aDirectory );
  322. path.Assign( dir, m_filename, getFileExt() );
  323. }
  324. if( !m_createIfMissing && !path.FileExists() )
  325. {
  326. wxLogTrace( traceSettings,
  327. wxT( "File for %s doesn't exist and m_createIfMissing == false; not saving" ),
  328. GetFullFilename() );
  329. return false;
  330. }
  331. // Ensure the path exists, and create it if not.
  332. if( !path.DirExists() && !path.Mkdir() )
  333. {
  334. wxLogTrace( traceSettings, wxT( "Warning: could not create path %s, can't save %s" ),
  335. path.GetPath(), GetFullFilename() );
  336. return false;
  337. }
  338. if( ( path.FileExists() && !path.IsFileWritable() ) ||
  339. ( !path.FileExists() && !path.IsDirWritable() ) )
  340. {
  341. wxLogTrace( traceSettings, wxT( "File for %s is read-only; not saving" ), GetFullFilename() );
  342. return false;
  343. }
  344. bool modified = false;
  345. for( auto settings : m_nested_settings )
  346. modified |= settings->SaveToFile();
  347. modified |= Store();
  348. if( !modified && !aForce && path.FileExists() )
  349. {
  350. wxLogTrace( traceSettings, wxT( "%s contents not modified, skipping save" ), GetFullFilename() );
  351. return false;
  352. }
  353. else if( !modified && !aForce && !m_createIfDefault )
  354. {
  355. wxLogTrace( traceSettings,
  356. wxT( "%s contents still default and m_createIfDefault == false; not saving" ),
  357. GetFullFilename() );
  358. return false;
  359. }
  360. wxLogTrace( traceSettings, wxT( "Saving %s" ), GetFullFilename() );
  361. LOCALE_IO dummy;
  362. bool success = true;
  363. try
  364. {
  365. std::stringstream buffer;
  366. buffer << std::setw( 2 ) << *m_internals << std::endl;
  367. wxFFileOutputStream fileStream( path.GetFullPath(), "wb" );
  368. if( !fileStream.IsOk()
  369. || !fileStream.WriteAll( buffer.str().c_str(), buffer.str().size() ) )
  370. {
  371. wxLogTrace( traceSettings, wxT( "Warning: could not save %s" ), GetFullFilename() );
  372. success = false;
  373. }
  374. }
  375. catch( nlohmann::json::exception& error )
  376. {
  377. wxLogTrace( traceSettings, wxT( "Catch error: could not save %s. Json error %s" ),
  378. GetFullFilename(), error.what() );
  379. success = false;
  380. }
  381. catch( ... )
  382. {
  383. wxLogTrace( traceSettings, wxT( "Error: could not save %s." ) );
  384. success = false;
  385. }
  386. return success;
  387. }
  388. const std::string JSON_SETTINGS::FormatAsString() const
  389. {
  390. LOCALE_IO dummy;
  391. std::stringstream buffer;
  392. buffer << std::setw( 2 ) << *m_internals << std::endl;
  393. return buffer.str();
  394. }
  395. bool JSON_SETTINGS::LoadFromRawFile( const wxString& aPath )
  396. {
  397. try
  398. {
  399. wxFFileInputStream fp( aPath, wxT( "rt" ) );
  400. wxStdInputStream fstream( fp );
  401. if( fp.IsOk() )
  402. {
  403. *static_cast<nlohmann::json*>( m_internals.get() ) =
  404. nlohmann::json::parse( fstream, nullptr,
  405. /* allow_exceptions = */ true,
  406. /* ignore_comments = */ true );
  407. }
  408. else
  409. {
  410. return false;
  411. }
  412. }
  413. catch( nlohmann::json::parse_error& error )
  414. {
  415. wxLogTrace( traceSettings, wxT( "Json parse error reading %s: %s" ), aPath, error.what() );
  416. return false;
  417. }
  418. // Now that we have new data in the JSON structure, load the params again
  419. Load();
  420. return true;
  421. }
  422. std::optional<nlohmann::json> JSON_SETTINGS::GetJson( const std::string& aPath ) const
  423. {
  424. nlohmann::json::json_pointer ptr = m_internals->PointerFromString( aPath );
  425. if( m_internals->contains( ptr ) )
  426. {
  427. try
  428. {
  429. return std::optional<nlohmann::json>{ m_internals->at( ptr ) };
  430. }
  431. catch( ... )
  432. {
  433. }
  434. }
  435. return std::optional<nlohmann::json>{};
  436. }
  437. template<typename ValueType>
  438. std::optional<ValueType> JSON_SETTINGS::Get( const std::string& aPath ) const
  439. {
  440. if( std::optional<nlohmann::json> ret = GetJson( aPath ) )
  441. {
  442. try
  443. {
  444. return ret->get<ValueType>();
  445. }
  446. catch( ... )
  447. {
  448. }
  449. }
  450. return std::nullopt;
  451. }
  452. // Instantiate all required templates here to allow reducing scope of json.hpp
  453. template std::optional<bool> JSON_SETTINGS::Get<bool>( const std::string& aPath ) const;
  454. template std::optional<double> JSON_SETTINGS::Get<double>( const std::string& aPath ) const;
  455. template std::optional<float> JSON_SETTINGS::Get<float>( const std::string& aPath ) const;
  456. template std::optional<int> JSON_SETTINGS::Get<int>( const std::string& aPath ) const;
  457. template std::optional<unsigned int> JSON_SETTINGS::Get<unsigned int>( const std::string& aPath ) const;
  458. template std::optional<unsigned long long> JSON_SETTINGS::Get<unsigned long long>( const std::string& aPath ) const;
  459. template std::optional<std::string> JSON_SETTINGS::Get<std::string>( const std::string& aPath ) const;
  460. template std::optional<nlohmann::json> JSON_SETTINGS::Get<nlohmann::json>( const std::string& aPath ) const;
  461. template std::optional<KIGFX::COLOR4D> JSON_SETTINGS::Get<KIGFX::COLOR4D>( const std::string& aPath ) const;
  462. template<typename ValueType>
  463. void JSON_SETTINGS::Set( const std::string& aPath, ValueType aVal )
  464. {
  465. m_internals->SetFromString( aPath, aVal );
  466. }
  467. // Instantiate all required templates here to allow reducing scope of json.hpp
  468. template void JSON_SETTINGS::Set<bool>( const std::string& aPath, bool aValue );
  469. template void JSON_SETTINGS::Set<double>( const std::string& aPath, double aValue );
  470. template void JSON_SETTINGS::Set<float>( const std::string& aPath, float aValue );
  471. template void JSON_SETTINGS::Set<int>( const std::string& aPath, int aValue );
  472. template void JSON_SETTINGS::Set<unsigned int>( const std::string& aPath, unsigned int aValue );
  473. template void JSON_SETTINGS::Set<unsigned long long>( const std::string& aPath, unsigned long long aValue );
  474. template void JSON_SETTINGS::Set<const char*>( const std::string& aPath, const char* aValue );
  475. template void JSON_SETTINGS::Set<std::string>( const std::string& aPath, std::string aValue );
  476. template void JSON_SETTINGS::Set<nlohmann::json>( const std::string& aPath, nlohmann::json aValue );
  477. template void JSON_SETTINGS::Set<KIGFX::COLOR4D>( const std::string& aPath, KIGFX::COLOR4D aValue );
  478. void JSON_SETTINGS::registerMigration( int aOldSchemaVersion, int aNewSchemaVersion,
  479. std::function<bool()> aMigrator )
  480. {
  481. wxASSERT( aNewSchemaVersion > aOldSchemaVersion );
  482. wxASSERT( aNewSchemaVersion <= m_schemaVersion );
  483. m_migrators[aOldSchemaVersion] = std::make_pair( aNewSchemaVersion, aMigrator );
  484. }
  485. bool JSON_SETTINGS::Migrate()
  486. {
  487. int filever = m_internals->Get<int>( "meta.version" );
  488. while( filever < m_schemaVersion )
  489. {
  490. if( !m_migrators.count( filever ) )
  491. {
  492. wxLogTrace( traceSettings, wxT( "Migrator missing for %s version %d!" ),
  493. typeid( *this ).name(), filever );
  494. return false;
  495. }
  496. std::pair<int, std::function<bool()>> pair = m_migrators.at( filever );
  497. if( pair.second() )
  498. {
  499. wxLogTrace( traceSettings, wxT( "Migrated %s from %d to %d" ), typeid( *this ).name(),
  500. filever, pair.first );
  501. filever = pair.first;
  502. m_internals->At( "meta.version" ) = filever;
  503. }
  504. else
  505. {
  506. wxLogTrace( traceSettings, wxT( "Migration failed for %s from %d to %d" ),
  507. typeid( *this ).name(), filever, pair.first );
  508. return false;
  509. }
  510. }
  511. return true;
  512. }
  513. bool JSON_SETTINGS::MigrateFromLegacy( wxConfigBase* aLegacyConfig )
  514. {
  515. wxLogTrace( traceSettings,
  516. wxT( "MigrateFromLegacy() not implemented for %s" ), typeid( *this ).name() );
  517. return false;
  518. }
  519. bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
  520. wxString& aTarget )
  521. {
  522. nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
  523. if( aObj.contains( ptr ) && aObj.at( ptr ).is_string() )
  524. {
  525. aTarget = aObj.at( ptr ).get<wxString>();
  526. return true;
  527. }
  528. return false;
  529. }
  530. bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
  531. bool& aTarget )
  532. {
  533. nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
  534. if( aObj.contains( ptr ) && aObj.at( ptr ).is_boolean() )
  535. {
  536. aTarget = aObj.at( ptr ).get<bool>();
  537. return true;
  538. }
  539. return false;
  540. }
  541. bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
  542. int& aTarget )
  543. {
  544. nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
  545. if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_integer() )
  546. {
  547. aTarget = aObj.at( ptr ).get<int>();
  548. return true;
  549. }
  550. return false;
  551. }
  552. bool JSON_SETTINGS::SetIfPresent( const nlohmann::json& aObj, const std::string& aPath,
  553. unsigned int& aTarget )
  554. {
  555. nlohmann::json::json_pointer ptr = JSON_SETTINGS_INTERNALS::PointerFromString( aPath );
  556. if( aObj.contains( ptr ) && aObj.at( ptr ).is_number_unsigned() )
  557. {
  558. aTarget = aObj.at( ptr ).get<unsigned int>();
  559. return true;
  560. }
  561. return false;
  562. }
  563. template<typename ValueType>
  564. bool JSON_SETTINGS::fromLegacy( wxConfigBase* aConfig, const std::string& aKey,
  565. const std::string& aDest )
  566. {
  567. ValueType val;
  568. if( aConfig->Read( aKey, &val ) )
  569. {
  570. try
  571. {
  572. ( *m_internals )[aDest] = val;
  573. }
  574. catch( ... )
  575. {
  576. wxASSERT_MSG( false, wxT( "Could not write value in fromLegacy!" ) );
  577. return false;
  578. }
  579. return true;
  580. }
  581. return false;
  582. }
  583. // Explicitly declare these because we only support a few types anyway, and it means we can keep
  584. // wxConfig detail out of the header file
  585. template bool JSON_SETTINGS::fromLegacy<int>( wxConfigBase*, const std::string&,
  586. const std::string& );
  587. template bool JSON_SETTINGS::fromLegacy<double>( wxConfigBase*, const std::string&,
  588. const std::string& );
  589. template bool JSON_SETTINGS::fromLegacy<bool>( wxConfigBase*, const std::string&,
  590. const std::string& );
  591. bool JSON_SETTINGS::fromLegacyString( wxConfigBase* aConfig, const std::string& aKey,
  592. const std::string& aDest )
  593. {
  594. wxString str;
  595. if( aConfig->Read( aKey, &str ) )
  596. {
  597. try
  598. {
  599. ( *m_internals )[aDest] = str.ToUTF8();
  600. }
  601. catch( ... )
  602. {
  603. wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyString!" ) );
  604. return false;
  605. }
  606. return true;
  607. }
  608. return false;
  609. }
  610. bool JSON_SETTINGS::fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
  611. const std::string& aDest )
  612. {
  613. wxString str;
  614. if( aConfig->Read( aKey, &str ) )
  615. {
  616. KIGFX::COLOR4D color;
  617. color.SetFromWxString( str );
  618. try
  619. {
  620. nlohmann::json js = nlohmann::json::array( { color.r, color.g, color.b, color.a } );
  621. ( *m_internals )[aDest] = js;
  622. }
  623. catch( ... )
  624. {
  625. wxASSERT_MSG( false, wxT( "Could not write value in fromLegacyColor!" ) );
  626. return false;
  627. }
  628. return true;
  629. }
  630. return false;
  631. }
  632. void JSON_SETTINGS::AddNestedSettings( NESTED_SETTINGS* aSettings )
  633. {
  634. wxLogTrace( traceSettings, wxT( "AddNestedSettings %s" ), aSettings->GetFilename() );
  635. m_nested_settings.push_back( aSettings );
  636. }
  637. void JSON_SETTINGS::ReleaseNestedSettings( NESTED_SETTINGS* aSettings )
  638. {
  639. if( !aSettings || !m_manager )
  640. return;
  641. auto it = std::find_if( m_nested_settings.begin(), m_nested_settings.end(),
  642. [&aSettings]( const JSON_SETTINGS* aPtr ) {
  643. return aPtr == aSettings;
  644. } );
  645. if( it != m_nested_settings.end() )
  646. {
  647. wxLogTrace( traceSettings, wxT( "Flush and release %s" ), ( *it )->GetFilename() );
  648. ( *it )->SaveToFile();
  649. m_nested_settings.erase( it );
  650. }
  651. aSettings->SetParent( nullptr );
  652. }
  653. // Specializations to allow conversion between wxString and std::string via JSON_SETTINGS API
  654. template<> std::optional<wxString> JSON_SETTINGS::Get( const std::string& aPath ) const
  655. {
  656. if( std::optional<nlohmann::json> opt_json = GetJson( aPath ) )
  657. return wxString( opt_json->get<std::string>().c_str(), wxConvUTF8 );
  658. return std::nullopt;
  659. }
  660. template<> void JSON_SETTINGS::Set<wxString>( const std::string& aPath, wxString aVal )
  661. {
  662. ( *m_internals )[aPath] = aVal.ToUTF8();
  663. }
  664. // Specializations to allow directly reading/writing wxStrings from JSON
  665. void to_json( nlohmann::json& aJson, const wxString& aString )
  666. {
  667. aJson = aString.ToUTF8();
  668. }
  669. void from_json( const nlohmann::json& aJson, wxString& aString )
  670. {
  671. aString = wxString( aJson.get<std::string>().c_str(), wxConvUTF8 );
  672. }
  673. template<typename ResultType>
  674. ResultType JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson, const std::string& aKey,
  675. ResultType aDefault )
  676. {
  677. ResultType ret = aDefault;
  678. try
  679. {
  680. if( aJson.contains( aKey ) )
  681. ret = aJson.at( aKey ).get<ResultType>();
  682. }
  683. catch( ... )
  684. {
  685. }
  686. return ret;
  687. }
  688. template std::string JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson,
  689. const std::string& aKey, std::string aDefault );
  690. template bool JSON_SETTINGS::fetchOrDefault( const nlohmann::json& aJson, const std::string& aKey,
  691. bool aDefault );