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