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.

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