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.

477 lines
17 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. #ifndef _SETTINGS_MANAGER_H
  21. #define _SETTINGS_MANAGER_H
  22. #include <algorithm>
  23. #include <mutex>
  24. #include <typeinfo>
  25. #include <core/wx_stl_compat.h> // for wxString hash
  26. #include <settings/color_settings.h>
  27. class COLOR_SETTINGS;
  28. class COMMON_SETTINGS;
  29. class KIWAY;
  30. class PROJECT;
  31. class PROJECT_FILE;
  32. class REPORTER;
  33. class wxSingleInstanceChecker;
  34. class wxFileName;
  35. class LOCKFILE;
  36. /// Project settings path will be <projectname> + this
  37. #define PROJECT_BACKUPS_DIR_SUFFIX wxT( "-backups" )
  38. class KICOMMON_API SETTINGS_MANAGER
  39. {
  40. public:
  41. SETTINGS_MANAGER( bool aHeadless = false );
  42. ~SETTINGS_MANAGER();
  43. /**
  44. * @return true if settings load was successful
  45. */
  46. bool IsOK() { return m_ok; }
  47. /**
  48. * Associate this setting manager with the given Kiway.
  49. *
  50. * @param aKiway is the kiway this settings manager should use
  51. */
  52. void SetKiway( KIWAY* aKiway ) { m_kiway = aKiway; }
  53. /**
  54. * Takes ownership of the pointer passed in
  55. * @param aSettings is a settings object to register
  56. * @return a handle to the owned pointer
  57. */
  58. template<typename T>
  59. T* RegisterSettings( T* aSettings, bool aLoadNow = true )
  60. {
  61. return static_cast<T*>( registerSettings( aSettings, aLoadNow ) );
  62. }
  63. void Load();
  64. void Load( JSON_SETTINGS* aSettings );
  65. void Save();
  66. void Save( JSON_SETTINGS* aSettings );
  67. /**
  68. * If the given settings object is registered, save it to disk and unregister it
  69. * @param aSettings is the object to release
  70. */
  71. void FlushAndRelease( JSON_SETTINGS* aSettings, bool aSave = true );
  72. /**
  73. * Returns a handle to the a given settings by type
  74. * If the settings have already been loaded, returns the existing pointer.
  75. * If the settings have not been loaded, creates a new object owned by the
  76. * settings manager and returns a pointer to it.
  77. *
  78. * @tparam T is a type derived from APP_SETTINGS_BASE
  79. * @param aLoadNow is true to load the registered file from disk immediately
  80. * @return a pointer to a loaded settings object
  81. */
  82. template<typename T>
  83. T* GetAppSettings()
  84. {
  85. T* ret = nullptr;
  86. size_t typeHash = typeid( T ).hash_code();
  87. if( m_app_settings_cache.count( typeHash ) )
  88. ret = static_cast<T*>( m_app_settings_cache.at( typeHash ) );
  89. if( ret )
  90. return ret;
  91. auto it = std::find_if( m_settings.begin(), m_settings.end(),
  92. []( const std::unique_ptr<JSON_SETTINGS>& aSettings )
  93. {
  94. return dynamic_cast<T*>( aSettings.get() );
  95. } );
  96. if( it != m_settings.end() )
  97. {
  98. ret = dynamic_cast<T*>( it->get() );
  99. }
  100. else
  101. {
  102. throw std::runtime_error( "Tried to GetAppSettings before registering" );
  103. }
  104. m_app_settings_cache[typeHash] = ret;
  105. return ret;
  106. }
  107. template<typename T>
  108. T* GetAppSettings( const wxString& aFilename )
  109. {
  110. #ifndef __WXMAC__
  111. return GetAppSettings<T>();
  112. #else
  113. T* ret = nullptr;
  114. size_t typeHash = typeid( T ).hash_code();
  115. if( m_app_settings_cache.count( typeHash ) )
  116. ret = static_cast<T*>( m_app_settings_cache.at( typeHash ) );
  117. if( ret )
  118. return ret;
  119. auto it = std::find_if( m_settings.begin(), m_settings.end(),
  120. [&]( const std::unique_ptr<JSON_SETTINGS>& aSettings )
  121. {
  122. return aSettings->GetFilename() == aFilename;
  123. } );
  124. if( it != m_settings.end() )
  125. {
  126. ret = static_cast<T*>( it->get() );
  127. }
  128. else
  129. {
  130. throw std::runtime_error( "Tried to GetAppSettings before registering" );
  131. }
  132. m_app_settings_cache[typeHash] = ret;
  133. return ret;
  134. #endif
  135. }
  136. /**
  137. * Retrieves a color settings object that applications can read colors from.
  138. * If the given settings file cannot be found, returns the default settings.
  139. *
  140. * @param aName is the name of the color scheme to load
  141. * @return a loaded COLOR_SETTINGS object
  142. */
  143. COLOR_SETTINGS* GetColorSettings( const wxString& aName = "user" );
  144. std::vector<COLOR_SETTINGS*> GetColorSettingsList()
  145. {
  146. std::vector<COLOR_SETTINGS*> ret;
  147. for( const std::pair<const wxString, COLOR_SETTINGS*>& entry : m_color_settings )
  148. ret.push_back( entry.second );
  149. std::sort( ret.begin(), ret.end(), []( COLOR_SETTINGS* a, COLOR_SETTINGS* b )
  150. {
  151. return a->GetName() < b->GetName();
  152. } );
  153. return ret;
  154. }
  155. /**
  156. * Safely saves a COLOR_SETTINGS to disk, preserving any changes outside the given namespace.
  157. *
  158. * A color settings namespace is one of the top-level JSON objects like "board", etc.
  159. * This will perform a read-modify-write
  160. *
  161. * @param aSettings is a pointer to a valid COLOR_SETTINGS object managed by SETTINGS_MANAGER
  162. * @param aNamespace is the namespace of settings to save
  163. */
  164. void SaveColorSettings( COLOR_SETTINGS* aSettings, const std::string& aNamespace = "" );
  165. /**
  166. * Registers a new color settings object with the given filename
  167. * @param aFilename is the location to store the new settings object
  168. * @return a pointer to the new object
  169. */
  170. COLOR_SETTINGS* AddNewColorSettings( const wxString& aFilename );
  171. /**
  172. * Returns a color theme for storing colors migrated from legacy (5.x and earlier) settings,
  173. * creating the theme if necessary. This theme will be called "user.json" / "User".
  174. * @return the color settings to be used for migrating legacy settings
  175. */
  176. COLOR_SETTINGS* GetMigratedColorSettings();
  177. /**
  178. * Retrieves the common settings shared by all applications
  179. * @return a pointer to a loaded COMMON_SETTINGS
  180. */
  181. COMMON_SETTINGS* GetCommonSettings() const { return m_common_settings; }
  182. /**
  183. * Returns the path a given settings file should be loaded from / stored to.
  184. * @param aSettings is the settings object
  185. * @return a path based on aSettings->m_location
  186. */
  187. wxString GetPathForSettingsFile( JSON_SETTINGS* aSettings );
  188. /**
  189. * Handles the initialization of the user settings directory and migration from previous
  190. * KiCad versions as needed.
  191. *
  192. * This method will check for the existence of the user settings path for this KiCad version.
  193. * If it exists, settings load will proceed normally using that path.
  194. *
  195. * If that directory is empty or does not exist, the migration wizard will be launched, which
  196. * will give users the option to migrate settings from a previous KiCad version (if one is
  197. * found), manually specify a directory to migrate fromm, or start with default settings.
  198. *
  199. * @return true if migration was successful or not necessary, false otherwise
  200. */
  201. bool MigrateIfNeeded();
  202. /**
  203. * Helper for DIALOG_MIGRATE_SETTINGS to specify a source for migration
  204. * @param aSource is a directory containing settings files to migrate from (can be empty)
  205. */
  206. void SetMigrationSource( const wxString& aSource ) { m_migration_source = aSource; }
  207. void SetMigrateLibraryTables( bool aMigrate = true ) { m_migrateLibraryTables = aMigrate; }
  208. /**
  209. * Retrieves the name of the most recent previous KiCad version that can be found in the
  210. * user settings directory. For legacy versions (5.x, and 5.99 builds before this code was
  211. * written), this will return "5.x"
  212. *
  213. * @param aName is filled with the name of the previous version, if one exists
  214. * @return true if a previous version to migrate from exists
  215. */
  216. bool GetPreviousVersionPaths( std::vector<wxString>* aName = nullptr );
  217. /**
  218. * Re-scans the color themes directory, reloading any changes it finds.
  219. */
  220. void ReloadColorSettings();
  221. /**
  222. * Loads a project or sets up a new project with a specified path
  223. * @param aFullPath is the full path to the project
  224. * @param aSetActive if true will set the loaded project as the active project
  225. * @return true if the PROJECT_FILE was successfully loaded from disk
  226. */
  227. bool LoadProject( const wxString& aFullPath, bool aSetActive = true );
  228. /**
  229. * Saves, unloads and unregisters the given PROJECT
  230. * @param aProject is the project object to unload
  231. * @param aSave if true will save the project before unloading
  232. * @return true if the PROJECT file was successfully saved
  233. */
  234. bool UnloadProject( PROJECT* aProject, bool aSave = true );
  235. /**
  236. * Helper for checking if we have a project open
  237. * TODO: This should be deprecated along with Prj() once we support multiple projects fully
  238. * @return true if a call to Prj() will succeed
  239. */
  240. bool IsProjectOpen() const;
  241. /**
  242. * Helper for checking if we have a project open that is not a dummy project
  243. * @return true if a call to Prj() will succeed and the project is not a dummy project
  244. */
  245. bool IsProjectOpenNotDummy() const;
  246. /**
  247. * A helper while we are not MDI-capable -- return the one and only project
  248. * @return the loaded project
  249. */
  250. PROJECT& Prj() const;
  251. /**
  252. * Retrieves a loaded project by name
  253. * @param aFullPath is the full path including name and extension to the project file
  254. * @return a pointer to the project if loaded, or nullptr
  255. */
  256. PROJECT* GetProject( const wxString& aFullPath ) const;
  257. /**
  258. * @return a list of open projects
  259. */
  260. std::vector<wxString> GetOpenProjects() const;
  261. /**
  262. * Saves a loaded project.
  263. * @param aFullPath is the project name to save. If empty, will save the first loaded project.
  264. * @param aProject is the project to save, or nullptr to save the active project (Prj() return)
  265. * @return true if save was successful
  266. */
  267. bool SaveProject( const wxString& aFullPath = wxEmptyString, PROJECT* aProject = nullptr );
  268. /**
  269. * Sets the currently loaded project path and saves it (pointers remain valid)
  270. * Note that this will not modify the read-only state of the project, so it will have no effect
  271. * if the project is marked as read-only!
  272. * @param aFullPath is the full filename to set for the project
  273. * @param aProject is the project to save, or nullptr to save the active project (Prj() return)
  274. */
  275. void SaveProjectAs( const wxString& aFullPath, PROJECT* aProject = nullptr );
  276. /**
  277. * Saves a copy of the current project under the given path. Will save the copy even if the
  278. * current project is marked as read-only.
  279. * @param aFullPath is the full filename to set for the project
  280. * @param aProject is the project to save, or nullptr to save the active project (Prj() return)
  281. */
  282. void SaveProjectCopy( const wxString& aFullPath, PROJECT* aProject = nullptr );
  283. /**
  284. * @return the full path to where project backups should be stored
  285. */
  286. wxString GetProjectBackupsPath() const;
  287. /**
  288. * Creates a backup archive of the current project
  289. * @param aReporter is used for progress reporting
  290. * @param aTarget is the full path to the backup file. If empty, will generate and return the
  291. * full path to the backup file.
  292. * @return true if everything succeeded
  293. */
  294. bool BackupProject( REPORTER& aReporter, wxFileName& aTarget ) const;
  295. /**
  296. * Calls BackupProject if a new backup is needed according to the current backup policy.
  297. * @param aReporter is used for progress reporting
  298. * @return if everything succeeded
  299. */
  300. bool TriggerBackupIfNeeded( REPORTER& aReporter ) const;
  301. /**
  302. * Checks if a given path is probably a valid KiCad configuration directory.
  303. * Actually it just checks if a file called "kicad_common" exists, because that's probably
  304. * good enough for now.
  305. *
  306. * @param aPath is the path to check
  307. * @return true if the path contains KiCad settings
  308. */
  309. static bool IsSettingsPathValid( const wxString& aPath );
  310. /**
  311. * Returns the path where color scheme files are stored; creating it if missing
  312. * (normally ./colors/ under the user settings path)
  313. */
  314. static wxString GetColorSettingsPath();
  315. /**
  316. * Parses the current KiCad build version and extracts the major and minor revision to use
  317. * as the name of the settings directory for this KiCad version.
  318. *
  319. * @return a string such as "5.1"
  320. */
  321. static std::string GetSettingsVersion();
  322. /**
  323. * A proxy for PATHS::GetUserSettingsPath rather than fighting swig
  324. */
  325. static wxString GetUserSettingsPath();
  326. private:
  327. JSON_SETTINGS* registerSettings( JSON_SETTINGS* aSettings, bool aLoadNow = true );
  328. /**
  329. * Compares two settings versions, like "5.99" and "6.0"
  330. * @return -1 if aFirst is older than aSecond, 1 if aFirst is newer than aSecond, 0 otherwise
  331. */
  332. static int compareVersions( const std::string& aFirst, const std::string& aSecond );
  333. /**
  334. * Extracts the numeric version from a given settings string
  335. * @param aVersionString is the string to split at the "."
  336. * @param aMajor will store the first part
  337. * @param aMinor will store the second part
  338. * @return true if extraction succeeded
  339. */
  340. static bool extractVersion( const std::string& aVersionString, int* aMajor = nullptr,
  341. int* aMinor = nullptr );
  342. /**
  343. * Attempts to load a color theme by name (the color theme directory and .json ext are assumed)
  344. * @param aName is the filename of the color theme (without the extension or path)
  345. * @return the loaded settings, or nullptr if load failed
  346. */
  347. COLOR_SETTINGS* loadColorSettingsByName( const wxString& aName );
  348. COLOR_SETTINGS* registerColorSettings( const wxString& aFilename, bool aAbsolutePath = false );
  349. void loadAllColorSettings();
  350. /**
  351. * Registers a PROJECT_FILE and attempts to load it from disk
  352. * @param aProject is the project object to load the file for
  353. * @return true if the PROJECT_FILE was successfully loaded
  354. */
  355. bool loadProjectFile( PROJECT& aProject );
  356. /**
  357. * Optionally saves, and then unloads and unregisters the given PROJECT_FILE
  358. * @param aProject is the project object to unload the file for
  359. * @param aSave if true will save the project file before unloading
  360. * @return true if the PROJECT file was successfully saved
  361. */
  362. bool unloadProjectFile( PROJECT* aProject, bool aSave );
  363. // Helper to create built-in colors and register them
  364. void registerBuiltinColorSettings();
  365. private:
  366. /// True if running outside a UI context
  367. bool m_headless;
  368. /// The kiway this settings manager interacts with
  369. KIWAY* m_kiway;
  370. std::vector<std::unique_ptr<JSON_SETTINGS>> m_settings;
  371. std::unordered_map<wxString, COLOR_SETTINGS*> m_color_settings;
  372. /// Cache for app settings
  373. std::unordered_map<size_t, JSON_SETTINGS*> m_app_settings_cache;
  374. // Convenience shortcut
  375. COMMON_SETTINGS* m_common_settings;
  376. wxString m_migration_source;
  377. /// If true, the symbol and footprint library tables will be migrated from the previous version
  378. bool m_migrateLibraryTables;
  379. /// True if settings loaded successfully at construction
  380. bool m_ok;
  381. /// Loaded projects (ownership here)
  382. std::vector<std::unique_ptr<PROJECT>> m_projects_list;
  383. /// Loaded projects, mapped according to project full name
  384. std::map<wxString, PROJECT*> m_projects;
  385. /// Loaded project files, mapped according to project full name
  386. std::map<wxString, PROJECT_FILE*> m_project_files;
  387. /// Lock for loaded project (expand to multiple once we support MDI)
  388. std::unique_ptr<LOCKFILE> m_project_lock;
  389. static wxString backupDateTimeFormat;
  390. };
  391. #endif