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.

522 lines
20 KiB

5 years ago
  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 <set>
  21. #include <paths.h>
  22. #include <search_stack.h>
  23. #include <settings/common_settings.h>
  24. #include <settings/json_settings_internals.h>
  25. #include <settings/parameters.h>
  26. #include <systemdirsappend.h>
  27. #include <trace_helpers.h>
  28. #include <wx/config.h>
  29. #include <wx/log.h>
  30. ///! The following environment variables will never be migrated from a previous version
  31. const std::set<wxString> envVarBlacklist =
  32. {
  33. wxT( "KICAD6_SYMBOL_DIR" ),
  34. wxT( "KICAD6_FOOTPRINT_DIR" ),
  35. wxT( "KICAD6_TEMPLATES_DIR" ),
  36. wxT( "KICAD6_3DMODEL_DIR" )
  37. };
  38. ///! Update the schema version whenever a migration is required
  39. const int commonSchemaVersion = 2;
  40. COMMON_SETTINGS::COMMON_SETTINGS() :
  41. JSON_SETTINGS( "kicad_common", SETTINGS_LOC::USER, commonSchemaVersion ),
  42. m_Appearance(),
  43. m_Backup(),
  44. m_Env(),
  45. m_Input(),
  46. m_Graphics(),
  47. m_Session(),
  48. m_System(),
  49. m_NetclassPanel()
  50. {
  51. // This only effect the first time KiCad is run. The user's setting will be used for all
  52. // subsequent runs.
  53. // Menu icons are off by default on OSX and on for all other platforms.
  54. // Use automatic canvas scaling on OSX, but not on the other platforms (their detection
  55. // isn't as good).
  56. #if defined( __WXMAC__ )
  57. bool defaultUseIconsInMenus = false;
  58. double canvasScale = 0.0;
  59. #else
  60. bool defaultUseIconsInMenus = true;
  61. double canvasScale = 1.0;
  62. #endif
  63. m_params.emplace_back( new PARAM<double>( "appearance.canvas_scale",
  64. &m_Appearance.canvas_scale, canvasScale ) );
  65. m_params.emplace_back( new PARAM<int>( "appearance.icon_scale",
  66. &m_Appearance.icon_scale, 0 ) );
  67. m_params.emplace_back( new PARAM_ENUM<ICON_THEME>( "appearance.icon_theme",
  68. &m_Appearance.icon_theme, ICON_THEME::AUTO, ICON_THEME::LIGHT, ICON_THEME::AUTO ) );
  69. m_params.emplace_back( new PARAM<bool>( "appearance.use_icons_in_menus",
  70. &m_Appearance.use_icons_in_menus, defaultUseIconsInMenus ) );
  71. m_params.emplace_back( new PARAM<bool>( "auto_backup.enabled", &m_Backup.enabled, true ) );
  72. m_params.emplace_back( new PARAM<bool>( "auto_backup.backup_on_autosave",
  73. &m_Backup.backup_on_autosave, false ) );
  74. m_params.emplace_back( new PARAM<int>( "auto_backup.limit_total_files",
  75. &m_Backup.limit_total_files, 25 ) );
  76. m_params.emplace_back( new PARAM<unsigned long long>( "auto_backup.limit_total_size",
  77. &m_Backup.limit_total_size, 104857600 ) );
  78. m_params.emplace_back( new PARAM<int>( "auto_backup.limit_daily_files",
  79. &m_Backup.limit_daily_files, 5 ) );
  80. m_params.emplace_back( new PARAM<int>( "auto_backup.min_interval",
  81. &m_Backup.min_interval, 300 ) );
  82. m_params.emplace_back( new PARAM<bool>( "environment.show_warning_dialog",
  83. &m_Env.show_warning_dialog, false ) );
  84. m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "environment.vars",
  85. [&]() -> nlohmann::json
  86. {
  87. nlohmann::json ret = {};
  88. for( const std::pair<wxString, ENV_VAR_ITEM> entry : m_Env.vars )
  89. {
  90. const ENV_VAR_ITEM& var = entry.second;
  91. wxASSERT( entry.first == var.GetKey() );
  92. // Default values are never persisted
  93. if( var.IsDefault() )
  94. {
  95. wxLogTrace( traceEnvVars,
  96. "COMMON_SETTINGS: Env var %s skipping save (default)",
  97. var.GetKey() );
  98. continue;
  99. }
  100. wxString value = var.GetValue();
  101. // Vars that existed in JSON are persisted, but if they were overridden
  102. // externally, we persist the old value (i.e. the one that was loaded from JSON)
  103. if( var.GetDefinedExternally() )
  104. {
  105. if( var.GetDefinedInSettings() )
  106. {
  107. wxLogTrace( traceEnvVars,
  108. "COMMON_SETTINGS: Env var %s was overridden externally, "
  109. "saving previously-loaded value %s",
  110. var.GetKey(), var.GetSettingsValue() );
  111. value = var.GetSettingsValue();
  112. }
  113. else
  114. {
  115. wxLogTrace( traceEnvVars,
  116. "COMMON_SETTINGS: Env var %s skipping save (external)",
  117. var.GetKey() );
  118. continue;
  119. }
  120. }
  121. wxLogTrace( traceEnvVars,
  122. "COMMON_SETTINGS: Saving env var %s = %s",
  123. var.GetKey(), value);
  124. std::string key( var.GetKey().ToUTF8() );
  125. ret[key] = value;
  126. }
  127. return ret;
  128. },
  129. [&]( const nlohmann::json& aJson )
  130. {
  131. if( !aJson.is_object() )
  132. return;
  133. for( const auto& entry : aJson.items() )
  134. {
  135. wxString key = wxString( entry.key().c_str(), wxConvUTF8 );
  136. wxString val = entry.value().get<wxString>();
  137. if( m_Env.vars.count( key ) )
  138. {
  139. if( m_Env.vars[key].GetDefinedExternally() )
  140. {
  141. wxLogTrace( traceEnvVars, "COMMON_SETTINGS: %s is defined externally",
  142. key );
  143. m_Env.vars[key].SetDefinedInSettings();
  144. m_Env.vars[key].SetSettingsValue( val );
  145. continue;
  146. }
  147. else
  148. {
  149. wxLogTrace( traceEnvVars, "COMMON_SETTINGS: Updating %s: %s -> %s",
  150. key, m_Env.vars[key].GetValue(), val );
  151. m_Env.vars[key].SetValue( val );
  152. }
  153. }
  154. else
  155. {
  156. wxLogTrace( traceEnvVars, "COMMON_SETTINGS: Loaded new var: %s = %s",
  157. key, val );
  158. m_Env.vars[key] = ENV_VAR_ITEM( key, val );
  159. }
  160. m_Env.vars[key].SetDefinedInSettings();
  161. m_Env.vars[key].SetSettingsValue( val );
  162. }
  163. },
  164. {} ) );
  165. m_params.emplace_back( new PARAM<bool>( "input.auto_pan", &m_Input.auto_pan, false ) );
  166. m_params.emplace_back( new PARAM<int>( "input.auto_pan_acceleration",
  167. &m_Input.auto_pan_acceleration, 5 ) );
  168. m_params.emplace_back( new PARAM<bool>( "input.center_on_zoom",
  169. &m_Input.center_on_zoom, true ) );
  170. m_params.emplace_back( new PARAM<bool>( "input.immediate_actions",
  171. &m_Input.immediate_actions, true ) );
  172. m_params.emplace_back( new PARAM<bool>( "input.warp_mouse_on_move",
  173. &m_Input.warp_mouse_on_move, true ) );
  174. m_params.emplace_back( new PARAM<bool>( "input.horizontal_pan",
  175. &m_Input.horizontal_pan, false ) );
  176. m_params.emplace_back( new PARAM<bool>( "input.zoom_acceleration",
  177. &m_Input.zoom_acceleration, false ) );
  178. #ifdef __WXMAC__
  179. int default_zoom_speed = 5;
  180. #else
  181. int default_zoom_speed = 1;
  182. #endif
  183. m_params.emplace_back( new PARAM<int>( "input.zoom_speed",
  184. &m_Input.zoom_speed, default_zoom_speed ) );
  185. m_params.emplace_back( new PARAM<bool>( "input.zoom_speed_auto",
  186. &m_Input.zoom_speed_auto, true ) );
  187. m_params.emplace_back( new PARAM<int>( "input.scroll_modifier_zoom",
  188. &m_Input.scroll_modifier_zoom, 0 ) );
  189. m_params.emplace_back( new PARAM<int>( "input.scroll_modifier_pan_h",
  190. &m_Input.scroll_modifier_pan_h, WXK_CONTROL ) );
  191. m_params.emplace_back( new PARAM<int>( "input.scroll_modifier_pan_v",
  192. &m_Input.scroll_modifier_pan_v, WXK_SHIFT ) );
  193. m_params.emplace_back( new PARAM_ENUM<MOUSE_DRAG_ACTION>( "input.mouse_left",
  194. &m_Input.drag_left, MOUSE_DRAG_ACTION::DRAG_SELECTED, MOUSE_DRAG_ACTION::DRAG_ANY,
  195. MOUSE_DRAG_ACTION::SELECT ) );
  196. m_params.emplace_back( new PARAM_ENUM<MOUSE_DRAG_ACTION>( "input.mouse_middle",
  197. &m_Input.drag_middle, MOUSE_DRAG_ACTION::PAN, MOUSE_DRAG_ACTION::SELECT,
  198. MOUSE_DRAG_ACTION::NONE ) );
  199. m_params.emplace_back( new PARAM_ENUM<MOUSE_DRAG_ACTION>( "input.mouse_right",
  200. &m_Input.drag_right, MOUSE_DRAG_ACTION::PAN, MOUSE_DRAG_ACTION::SELECT,
  201. MOUSE_DRAG_ACTION::NONE ) );
  202. m_params.emplace_back( new PARAM<int>( "graphics.opengl_antialiasing_mode",
  203. &m_Graphics.opengl_aa_mode, 0, 0, 4 ) );
  204. m_params.emplace_back( new PARAM<int>( "graphics.cairo_antialiasing_mode",
  205. &m_Graphics.cairo_aa_mode, 0, 0, 3 ) );
  206. m_params.emplace_back( new PARAM<int>( "system.autosave_interval",
  207. &m_System.autosave_interval, 600 ) );
  208. m_params.emplace_back( new PARAM<wxString>( "system.editor_name",
  209. &m_System.editor_name, "" ) );
  210. m_params.emplace_back( new PARAM<int>( "system.file_history_size",
  211. &m_System.file_history_size, 9 ) );
  212. m_params.emplace_back( new PARAM<wxString>( "system.language",
  213. &m_System.language, "Default" ) );
  214. m_params.emplace_back( new PARAM<wxString>( "system.pdf_viewer_name",
  215. &m_System.pdf_viewer_name, "" ) );
  216. m_params.emplace_back( new PARAM<bool>( "system.use_system_pdf_viewer",
  217. &m_System.use_system_pdf_viewer, true ) );
  218. m_params.emplace_back( new PARAM<wxString>( "system.working_dir",
  219. &m_System.working_dir, "" ) );
  220. m_params.emplace_back( new PARAM<int>( "system.clear_3d_cache_interval",
  221. &m_System.clear_3d_cache_interval, 30 ) );
  222. m_params.emplace_back( new PARAM<bool>( "session.remember_open_files",
  223. &m_Session.remember_open_files, false ) );
  224. m_params.emplace_back( new PARAM<int>( "netclass_panel.sash_pos",
  225. &m_NetclassPanel.sash_pos, 160 ) );
  226. registerMigration( 0, 1, std::bind( &COMMON_SETTINGS::migrateSchema0to1, this ) );
  227. registerMigration( 1, 2, std::bind( &COMMON_SETTINGS::migrateSchema1to2, this ) );
  228. }
  229. bool COMMON_SETTINGS::migrateSchema0to1()
  230. {
  231. /**
  232. * Schema version 0 to 1:
  233. *
  234. * mousewheel_pan is replaced by explicit settings for scroll wheel behavior
  235. */
  236. nlohmann::json::json_pointer mwp_pointer( "/input/mousewheel_pan"_json_pointer );
  237. bool mwp = false;
  238. try
  239. {
  240. mwp = m_internals->at( mwp_pointer );
  241. m_internals->At( "input" ).erase( "mousewheel_pan" );
  242. }
  243. catch( ... )
  244. {
  245. wxLogTrace( traceSettings, "COMMON_SETTINGS::Migrate 0->1: mousewheel_pan not found" );
  246. }
  247. if( mwp )
  248. {
  249. ( *m_internals )[nlohmann::json::json_pointer( "/input/horizontal_pan" )] = true;
  250. ( *m_internals )[nlohmann::json::json_pointer( "/input/scroll_modifier_pan_h" )] = WXK_SHIFT;
  251. ( *m_internals )[nlohmann::json::json_pointer( "/input/scroll_modifier_pan_v" )] = 0;
  252. ( *m_internals )[nlohmann::json::json_pointer( "/input/scroll_modifier_zoom" )] = WXK_CONTROL;
  253. }
  254. else
  255. {
  256. ( *m_internals )[nlohmann::json::json_pointer( "/input/horizontal_pan" )] = false;
  257. ( *m_internals )[nlohmann::json::json_pointer( "/input/scroll_modifier_pan_h" )] = WXK_CONTROL;
  258. ( *m_internals )[nlohmann::json::json_pointer( "/input/scroll_modifier_pan_v" )] = WXK_SHIFT;
  259. ( *m_internals )[nlohmann::json::json_pointer( "/input/scroll_modifier_zoom" )] = 0;
  260. }
  261. return true;
  262. }
  263. bool COMMON_SETTINGS::migrateSchema1to2()
  264. {
  265. nlohmann::json::json_pointer v1_pointer( "/input/prefer_select_to_drag"_json_pointer );
  266. bool prefer_selection = false;
  267. try
  268. {
  269. prefer_selection = m_internals->at( v1_pointer );
  270. m_internals->at( nlohmann::json::json_pointer( "/input"_json_pointer ) ).erase( "prefer_select_to_drag" );
  271. }
  272. catch( ... )
  273. {
  274. wxLogTrace( traceSettings, "COMMON_SETTINGS::Migrate 1->2: prefer_select_to_drag not found" );
  275. }
  276. if( prefer_selection )
  277. ( *m_internals )[nlohmann::json::json_pointer( "/input/mouse_left" )] = MOUSE_DRAG_ACTION::SELECT;
  278. else
  279. ( *m_internals )[nlohmann::json::json_pointer( "/input/mouse_left" )] = MOUSE_DRAG_ACTION::DRAG_ANY;
  280. return true;
  281. }
  282. bool COMMON_SETTINGS::MigrateFromLegacy( wxConfigBase* aCfg )
  283. {
  284. bool ret = true;
  285. ret &= fromLegacy<double>( aCfg, "CanvasScale", "appearance.canvas_scale" );
  286. ret &= fromLegacy<int>( aCfg, "IconScale", "appearance.icon_scale" );
  287. ret &= fromLegacy<bool>( aCfg, "UseIconsInMenus", "appearance.use_icons_in_menus" );
  288. // Force OSX to automatically scale the canvas. Before v6, the user setting wasn't used on OSX
  289. // and was set to 1.0. In v6, the setting is now used by OSX and should default to automatic
  290. // scaling.
  291. #ifdef __WXMAC__
  292. Set( "appearance.canvas_scale", 0.0 );
  293. #endif
  294. ret &= fromLegacy<bool>( aCfg, "ShowEnvVarWarningDialog", "environment.show_warning_dialog" );
  295. auto load_env_vars = [&] () {
  296. wxString key, value;
  297. long index = 0;
  298. nlohmann::json::json_pointer ptr = m_internals->PointerFromString( "environment.vars" );
  299. aCfg->SetPath( "EnvironmentVariables" );
  300. ( *m_internals )[ptr] = nlohmann::json( {} );
  301. while( aCfg->GetNextEntry( key, index ) )
  302. {
  303. if( envVarBlacklist.count( key ) )
  304. {
  305. wxLogTrace( traceSettings, "Migrate Env: %s is blacklisted; skipping.", key );
  306. continue;
  307. }
  308. value = aCfg->Read( key, wxEmptyString );
  309. if( !value.IsEmpty() )
  310. {
  311. ptr.push_back( key.ToStdString() );
  312. wxLogTrace( traceSettings, "Migrate Env: %s=%s", ptr.to_string(), value );
  313. ( *m_internals )[ptr] = value.ToUTF8();
  314. ptr.pop_back();
  315. }
  316. }
  317. aCfg->SetPath( ".." );
  318. };
  319. load_env_vars();
  320. bool mousewheel_pan = false;
  321. if( aCfg->Read( "MousewheelPAN", &mousewheel_pan ) && mousewheel_pan )
  322. {
  323. Set( "input.horizontal_pan", true );
  324. Set( "input.scroll_modifier_pan_h", static_cast<int>( WXK_SHIFT ) );
  325. Set( "input.scroll_modifier_pan_v", 0 );
  326. Set( "input.scroll_modifier_zoom", static_cast<int>( WXK_CONTROL ) );
  327. }
  328. ret &= fromLegacy<bool>( aCfg, "AutoPAN", "input.auto_pan" );
  329. ret &= fromLegacy<bool>( aCfg, "ImmediateActions", "input.immediate_actions" );
  330. ret &= fromLegacy<bool>( aCfg, "PreferSelectionToDragging", "input.prefer_select_to_drag" );
  331. ret &= fromLegacy<bool>( aCfg, "MoveWarpsCursor", "input.warp_mouse_on_move" );
  332. ret &= fromLegacy<bool>( aCfg, "ZoomNoCenter", "input.center_on_zoom" );
  333. // This was stored inverted in legacy config
  334. if( OPT<bool> value = Get<bool>( "input.center_on_zoom" ) )
  335. Set( "input.center_on_zoom", !( *value ) );
  336. ret &= fromLegacy<int>( aCfg, "OpenGLAntialiasingMode", "graphics.opengl_antialiasing_mode" );
  337. ret &= fromLegacy<int>( aCfg, "CairoAntialiasingMode", "graphics.cairo_antialiasing_mode" );
  338. ret &= fromLegacy<int>( aCfg, "AutoSaveInterval", "system.autosave_interval" );
  339. ret &= fromLegacyString( aCfg, "Editor", "system.editor_name" );
  340. ret &= fromLegacy<int>( aCfg, "FileHistorySize", "system.file_history_size" );
  341. ret &= fromLegacyString( aCfg, "LanguageID", "system.language" );
  342. ret &= fromLegacyString( aCfg, "PdfBrowserName", "system.pdf_viewer_name" );
  343. ret &= fromLegacy<bool>( aCfg, "UseSystemBrowser", "system.use_system_pdf_viewer" );
  344. ret &= fromLegacyString( aCfg, "WorkingDir", "system.working_dir" );
  345. return ret;
  346. }
  347. void COMMON_SETTINGS::InitializeEnvironment()
  348. {
  349. auto addVar =
  350. [&]( const wxString& aKey, const wxString& aDefault )
  351. {
  352. m_Env.vars[aKey] = ENV_VAR_ITEM( aKey, aDefault, aDefault );
  353. wxString envValue;
  354. if( wxGetEnv( aKey, &envValue ) == true && !envValue.IsEmpty() )
  355. {
  356. m_Env.vars[aKey].SetValue( envValue );
  357. m_Env.vars[aKey].SetDefinedExternally();
  358. wxLogTrace( traceEnvVars,
  359. "InitializeEnvironment: Entry %s defined externally as %s", aKey,
  360. envValue );
  361. }
  362. else
  363. {
  364. wxLogTrace( traceEnvVars, "InitializeEnvironment: Setting entry %s to default %s",
  365. aKey, aDefault );
  366. }
  367. };
  368. wxFileName basePath( PATHS::GetStockEDALibraryPath(), wxEmptyString );
  369. wxFileName path( basePath );
  370. path.AppendDir( wxT( "modules" ) );
  371. addVar( wxT( "KICAD6_FOOTPRINT_DIR" ), path.GetFullPath() );
  372. path = basePath;
  373. path.AppendDir( wxT( "3dmodels" ) );
  374. addVar( wxT( "KICAD6_3DMODEL_DIR" ), path.GetFullPath() );
  375. // We don't have just one default template path, so use this logic that originally was in
  376. // PGM_BASE::InitPgm to determine the best default template path
  377. {
  378. // Attempt to find the best default template path.
  379. SEARCH_STACK bases;
  380. SEARCH_STACK templatePaths;
  381. SystemDirsAppend( &bases );
  382. for( unsigned i = 0; i < bases.GetCount(); ++i )
  383. {
  384. wxFileName fn( bases[i], wxEmptyString );
  385. // Add KiCad template file path to search path list.
  386. fn.AppendDir( "template" );
  387. // Only add path if exists and can be read by the user.
  388. if( fn.DirExists() && fn.IsDirReadable() )
  389. {
  390. wxLogTrace( tracePathsAndFiles, "Checking template path '%s' exists",
  391. fn.GetPath() );
  392. templatePaths.AddPaths( fn.GetPath() );
  393. }
  394. }
  395. if( templatePaths.IsEmpty() )
  396. {
  397. path = basePath;
  398. path.AppendDir( "template" );
  399. }
  400. else
  401. {
  402. // Take the first one. There may be more but this will likely be the best option.
  403. path.AssignDir( templatePaths[0] );
  404. }
  405. addVar( wxT( "KICAD6_TEMPLATE_DIR" ), path.GetFullPath() );
  406. }
  407. addVar( wxT( "KICAD_USER_TEMPLATE_DIR" ), PATHS::GetUserTemplatesPath() );
  408. path = basePath;
  409. path.AppendDir( wxT( "library" ) );
  410. addVar( wxT( "KICAD6_SYMBOL_DIR" ), path.GetFullPath() );
  411. }