KiCad PCB EDA Suite
common_settings.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2020 Jon Evans <[email protected]>
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 #include <set>
22 
23 #include <paths.h>
24 #include <search_stack.h>
27 #include <settings/parameters.h>
28 #include <systemdirsappend.h>
29 #include <trace_helpers.h>
30 #include <wx/config.h>
31 #include <wx/log.h>
32 
33 
35 const std::set<wxString> envVarBlacklist =
36  {
37  wxT( "KICAD6_SYMBOL_DIR" ),
38  wxT( "KICAD6_FOOTPRINT_DIR" ),
39  wxT( "KICAD6_TEMPLATES_DIR" ),
40  wxT( "KICAD6_3DMODEL_DIR" )
41  };
42 
43 
45 const int commonSchemaVersion = 2;
46 
49  m_Appearance(),
50  m_Backup(),
51  m_Env(),
52  m_Input(),
53  m_Graphics(),
54  m_Session(),
55  m_System(),
56  m_NetclassPanel()
57 {
58  /*
59  * Automatic dark mode detection works fine on Mac.
60  */
61 #if defined( __WXGTK__ ) || defined( __WXMSW__ )
62  m_params.emplace_back( new PARAM_ENUM<ICON_THEME>( "appearance.icon_theme",
64 #else
66 #endif
67 
68  /*
69  * Automatic icon scaling works fine on Mac. It works mostly fine on MSW, but perhaps not
70  * uniformly enough to exclude the explicit controls there.
71  */
72 #if defined( __WXGTK__ ) || defined( __WXMSW__ )
73  m_params.emplace_back( new PARAM<int>( "appearance.icon_scale",
74  &m_Appearance.icon_scale, 0 ) );
75 #else
77 #endif
78 
79  /*
80  * Automatic canvas scaling works fine on Mac and MSW, and on GTK under wxWidgets 3.1 or
81  * better.
82  */
83 #if defined( __WXGTK__ ) && !wxCHECK_VERSION( 3, 1, 0 )
84  m_params.emplace_back( new PARAM<double>( "appearance.canvas_scale",
85  &m_Appearance.canvas_scale, 1.0 ) );
86 #else
88 #endif
89 
90  /*
91  * Menu icons are off by default on OSX and on for all other platforms.
92  */
93 #ifdef __WXMAC__
94  m_params.emplace_back( new PARAM<bool>( "appearance.use_icons_in_menus",
95  &m_Appearance.use_icons_in_menus, false ) );
96 #else
97  m_params.emplace_back( new PARAM<bool>( "appearance.use_icons_in_menus",
99 #endif
100 
101  /*
102  * Font scaling hacks are only needed on GTK under wxWidgets 3.0.
103  */
104 #if defined( __WXGTK__ ) && !wxCHECK_VERSION( 3, 1, 0 )
105  m_params.emplace_back( new PARAM<bool>( "appearance.apply_icon_scale_to_fonts",
107 #else
109 #endif
110 
111  m_params.emplace_back( new PARAM<bool>( "auto_backup.enabled", &m_Backup.enabled, true ) );
112 
113  m_params.emplace_back( new PARAM<bool>( "auto_backup.backup_on_autosave",
114  &m_Backup.backup_on_autosave, false ) );
115 
116  m_params.emplace_back( new PARAM<int>( "auto_backup.limit_total_files",
117  &m_Backup.limit_total_files, 25 ) );
118 
119  m_params.emplace_back( new PARAM<unsigned long long>( "auto_backup.limit_total_size",
120  &m_Backup.limit_total_size, 104857600 ) );
121 
122  m_params.emplace_back( new PARAM<int>( "auto_backup.limit_daily_files",
123  &m_Backup.limit_daily_files, 5 ) );
124 
125  m_params.emplace_back( new PARAM<int>( "auto_backup.min_interval",
126  &m_Backup.min_interval, 300 ) );
127 
128  m_params.emplace_back( new PARAM_LAMBDA<nlohmann::json>( "environment.vars",
129  [&]() -> nlohmann::json
130  {
131  nlohmann::json ret = {};
132 
133  for( const std::pair<wxString, ENV_VAR_ITEM> entry : m_Env.vars )
134  {
135  const ENV_VAR_ITEM& var = entry.second;
136 
137  wxASSERT( entry.first == var.GetKey() );
138 
139  // Default values are never persisted
140  if( var.IsDefault() )
141  {
142  wxLogTrace( traceEnvVars,
143  "COMMON_SETTINGS: Env var %s skipping save (default)",
144  var.GetKey() );
145  continue;
146  }
147 
148  wxString value = var.GetValue();
149 
150  // Vars that existed in JSON are persisted, but if they were overridden
151  // externally, we persist the old value (i.e. the one that was loaded from JSON)
152  if( var.GetDefinedExternally() )
153  {
154  if( var.GetDefinedInSettings() )
155  {
156  wxLogTrace( traceEnvVars,
157  "COMMON_SETTINGS: Env var %s was overridden externally, "
158  "saving previously-loaded value %s",
159  var.GetKey(), var.GetSettingsValue() );
160  value = var.GetSettingsValue();
161  }
162  else
163  {
164  wxLogTrace( traceEnvVars,
165  "COMMON_SETTINGS: Env var %s skipping save (external)",
166  var.GetKey() );
167  continue;
168  }
169  }
170 
171  wxLogTrace( traceEnvVars,
172  "COMMON_SETTINGS: Saving env var %s = %s",
173  var.GetKey(), value);
174 
175  std::string key( var.GetKey().ToUTF8() );
176  ret[key] = value;
177  }
178 
179  return ret;
180  },
181  [&]( const nlohmann::json& aJson )
182  {
183  if( !aJson.is_object() )
184  return;
185 
186  for( const auto& entry : aJson.items() )
187  {
188  wxString key = wxString( entry.key().c_str(), wxConvUTF8 );
189  wxString val = entry.value().get<wxString>();
190 
191  if( m_Env.vars.count( key ) )
192  {
193  if( m_Env.vars[key].GetDefinedExternally() )
194  {
195  wxLogTrace( traceEnvVars, "COMMON_SETTINGS: %s is defined externally",
196  key );
197  m_Env.vars[key].SetDefinedInSettings();
198  m_Env.vars[key].SetSettingsValue( val );
199  continue;
200  }
201  else
202  {
203  wxLogTrace( traceEnvVars, "COMMON_SETTINGS: Updating %s: %s -> %s",
204  key, m_Env.vars[key].GetValue(), val );
205  m_Env.vars[key].SetValue( val );
206  }
207  }
208  else
209  {
210  wxLogTrace( traceEnvVars, "COMMON_SETTINGS: Loaded new var: %s = %s",
211  key, val );
212  m_Env.vars[key] = ENV_VAR_ITEM( key, val );
213  }
214 
215  m_Env.vars[key].SetDefinedInSettings();
216  m_Env.vars[key].SetSettingsValue( val );
217  }
218  },
219  {} ) );
220 
221  m_params.emplace_back( new PARAM<bool>( "input.auto_pan", &m_Input.auto_pan, false ) );
222 
223  m_params.emplace_back( new PARAM<int>( "input.auto_pan_acceleration",
224  &m_Input.auto_pan_acceleration, 5 ) );
225 
226  m_params.emplace_back( new PARAM<bool>( "input.center_on_zoom",
227  &m_Input.center_on_zoom, true ) );
228 
229  m_params.emplace_back( new PARAM<bool>( "input.immediate_actions",
230  &m_Input.immediate_actions, true ) );
231 
232  m_params.emplace_back( new PARAM<bool>( "input.warp_mouse_on_move",
233  &m_Input.warp_mouse_on_move, true ) );
234 
235  m_params.emplace_back( new PARAM<bool>( "input.horizontal_pan",
236  &m_Input.horizontal_pan, false ) );
237 
238  m_params.emplace_back( new PARAM<bool>( "input.zoom_acceleration",
239  &m_Input.zoom_acceleration, false ) );
240 
241 #ifdef __WXMAC__
242  int default_zoom_speed = 5;
243 #else
244  int default_zoom_speed = 1;
245 #endif
246 
247  m_params.emplace_back( new PARAM<int>( "input.zoom_speed",
248  &m_Input.zoom_speed, default_zoom_speed ) );
249 
250  m_params.emplace_back( new PARAM<bool>( "input.zoom_speed_auto",
251  &m_Input.zoom_speed_auto, true ) );
252 
253  m_params.emplace_back( new PARAM<int>( "input.scroll_modifier_zoom",
254  &m_Input.scroll_modifier_zoom, 0 ) );
255 
256  m_params.emplace_back( new PARAM<int>( "input.scroll_modifier_pan_h",
257  &m_Input.scroll_modifier_pan_h, WXK_CONTROL ) );
258 
259  m_params.emplace_back( new PARAM<int>( "input.scroll_modifier_pan_v",
260  &m_Input.scroll_modifier_pan_v, WXK_SHIFT ) );
261 
262  m_params.emplace_back( new PARAM_ENUM<MOUSE_DRAG_ACTION>( "input.mouse_left",
265 
266  m_params.emplace_back( new PARAM_ENUM<MOUSE_DRAG_ACTION>( "input.mouse_middle",
267  &m_Input.drag_middle, MOUSE_DRAG_ACTION::PAN, MOUSE_DRAG_ACTION::SELECT,
269 
270  m_params.emplace_back( new PARAM_ENUM<MOUSE_DRAG_ACTION>( "input.mouse_right",
273 
274  m_params.emplace_back( new PARAM<int>( "graphics.opengl_antialiasing_mode",
275  &m_Graphics.opengl_aa_mode, 0, 0, 2 ) );
276 
277  m_params.emplace_back( new PARAM<int>( "graphics.cairo_antialiasing_mode",
278  &m_Graphics.cairo_aa_mode, 0, 0, 2 ) );
279 
280  m_params.emplace_back( new PARAM<int>( "system.autosave_interval",
281  &m_System.autosave_interval, 600 ) );
282 
283 #ifdef __WXMAC__
284  m_params.emplace_back( new PARAM<wxString>( "system.text_editor",
285  &m_System.text_editor, "/usr/bin/open -e" ) );
286 #else
287  m_params.emplace_back( new PARAM<wxString>( "system.text_editor",
288  &m_System.text_editor, "" ) );
289 #endif
290 
291  m_params.emplace_back( new PARAM<int>( "system.file_history_size",
292  &m_System.file_history_size, 9 ) );
293 
294  m_params.emplace_back( new PARAM<wxString>( "system.language",
295  &m_System.language, "Default" ) );
296 
297  m_params.emplace_back( new PARAM<wxString>( "system.pdf_viewer_name",
298  &m_System.pdf_viewer_name, "" ) );
299 
300  m_params.emplace_back( new PARAM<bool>( "system.use_system_pdf_viewer",
301  &m_System.use_system_pdf_viewer, true ) );
302 
303  m_params.emplace_back( new PARAM<wxString>( "system.working_dir",
304  &m_System.working_dir, "" ) );
305 
306  m_params.emplace_back( new PARAM<int>( "system.clear_3d_cache_interval",
307  &m_System.clear_3d_cache_interval, 30 ) );
308 
309  m_params.emplace_back( new PARAM<bool>( "do_not_show_again.zone_fill_warning",
310  &m_DoNotShowAgain.zone_fill_warning, false ) );
311 
312  m_params.emplace_back( new PARAM<bool>( "do_not_show_again.env_var_overwrite_warning",
313  &m_DoNotShowAgain.env_var_overwrite_warning, false ) );
314 
315  m_params.emplace_back( new PARAM<bool>( "do_not_show_again.scaled_3d_models_warning",
316  &m_DoNotShowAgain.scaled_3d_models_warning, false ) );
317 
318  m_params.emplace_back( new PARAM<bool>( "session.remember_open_files",
319  &m_Session.remember_open_files, false ) );
320 
321  m_params.emplace_back( new PARAM<int>( "netclass_panel.sash_pos",
322  &m_NetclassPanel.sash_pos, 160 ) );
323 
324  registerMigration( 0, 1, std::bind( &COMMON_SETTINGS::migrateSchema0to1, this ) );
325  registerMigration( 1, 2, std::bind( &COMMON_SETTINGS::migrateSchema1to2, this ) );
326 }
327 
328 
330 {
337  nlohmann::json::json_pointer mwp_pointer( "/input/mousewheel_pan"_json_pointer );
338 
339  bool mwp = false;
340 
341  try
342  {
343  mwp = m_internals->at( mwp_pointer );
344  m_internals->At( "input" ).erase( "mousewheel_pan" );
345  }
346  catch( ... )
347  {
348  wxLogTrace( traceSettings, "COMMON_SETTINGS::Migrate 0->1: mousewheel_pan not found" );
349  }
350 
351  if( mwp )
352  {
353  ( *m_internals )[nlohmann::json::json_pointer( "/input/horizontal_pan" )] = true;
354 
355  ( *m_internals )[nlohmann::json::json_pointer( "/input/scroll_modifier_pan_h" )] = WXK_SHIFT;
356  ( *m_internals )[nlohmann::json::json_pointer( "/input/scroll_modifier_pan_v" )] = 0;
357  ( *m_internals )[nlohmann::json::json_pointer( "/input/scroll_modifier_zoom" )] = WXK_CONTROL;
358  }
359  else
360  {
361  ( *m_internals )[nlohmann::json::json_pointer( "/input/horizontal_pan" )] = false;
362 
363  ( *m_internals )[nlohmann::json::json_pointer( "/input/scroll_modifier_pan_h" )] = WXK_CONTROL;
364  ( *m_internals )[nlohmann::json::json_pointer( "/input/scroll_modifier_pan_v" )] = WXK_SHIFT;
365  ( *m_internals )[nlohmann::json::json_pointer( "/input/scroll_modifier_zoom" )] = 0;
366  }
367 
368  return true;
369 }
370 
371 
373 {
374  nlohmann::json::json_pointer v1_pointer( "/input/prefer_select_to_drag"_json_pointer );
375 
376  bool prefer_selection = false;
377 
378  try
379  {
380  prefer_selection = m_internals->at( v1_pointer );
381  m_internals->at( nlohmann::json::json_pointer( "/input"_json_pointer ) ).erase( "prefer_select_to_drag" );
382  }
383  catch( ... )
384  {
385  wxLogTrace( traceSettings, "COMMON_SETTINGS::Migrate 1->2: prefer_select_to_drag not found" );
386  }
387 
388  if( prefer_selection )
389  ( *m_internals )[nlohmann::json::json_pointer( "/input/mouse_left" )] = MOUSE_DRAG_ACTION::SELECT;
390  else
391  ( *m_internals )[nlohmann::json::json_pointer( "/input/mouse_left" )] = MOUSE_DRAG_ACTION::DRAG_ANY;
392 
393  return true;
394 }
395 
396 
397 bool COMMON_SETTINGS::MigrateFromLegacy( wxConfigBase* aCfg )
398 {
399  bool ret = true;
400 
401  ret &= fromLegacy<double>( aCfg, "CanvasScale", "appearance.canvas_scale" );
402  ret &= fromLegacy<int>( aCfg, "IconScale", "appearance.icon_scale" );
403  ret &= fromLegacy<bool>( aCfg, "UseIconsInMenus", "appearance.use_icons_in_menus" );
404  ret &= fromLegacy<bool>( aCfg, "ShowEnvVarWarningDialog", "environment.show_warning_dialog" );
405 
406  auto load_env_vars =
407  [&]()
408  {
409  wxString key, value;
410  long index = 0;
411  nlohmann::json::json_pointer ptr = m_internals->PointerFromString( "environment.vars" );
412 
413  aCfg->SetPath( "EnvironmentVariables" );
414  ( *m_internals )[ptr] = nlohmann::json( {} );
415 
416  while( aCfg->GetNextEntry( key, index ) )
417  {
418  if( envVarBlacklist.count( key ) )
419  {
420  wxLogTrace( traceSettings, "Migrate Env: %s is blacklisted; skipping.", key );
421  continue;
422  }
423 
424  value = aCfg->Read( key, wxEmptyString );
425 
426  if( !value.IsEmpty() )
427  {
428  ptr.push_back( key.ToStdString() );
429 
430  wxLogTrace( traceSettings, "Migrate Env: %s=%s", ptr.to_string(), value );
431  ( *m_internals )[ptr] = value.ToUTF8();
432 
433  ptr.pop_back();
434  }
435  }
436 
437  aCfg->SetPath( ".." );
438  };
439 
440  load_env_vars();
441 
442  bool mousewheel_pan = false;
443 
444  if( aCfg->Read( "MousewheelPAN", &mousewheel_pan ) && mousewheel_pan )
445  {
446  Set( "input.horizontal_pan", true );
447  Set( "input.scroll_modifier_pan_h", static_cast<int>( WXK_SHIFT ) );
448  Set( "input.scroll_modifier_pan_v", 0 );
449  Set( "input.scroll_modifier_zoom", static_cast<int>( WXK_CONTROL ) );
450  }
451 
452  ret &= fromLegacy<bool>( aCfg, "AutoPAN", "input.auto_pan" );
453  ret &= fromLegacy<bool>( aCfg, "ImmediateActions", "input.immediate_actions" );
454  ret &= fromLegacy<bool>( aCfg, "PreferSelectionToDragging", "input.prefer_select_to_drag" );
455  ret &= fromLegacy<bool>( aCfg, "MoveWarpsCursor", "input.warp_mouse_on_move" );
456  ret &= fromLegacy<bool>( aCfg, "ZoomNoCenter", "input.center_on_zoom" );
457 
458  // This was stored inverted in legacy config
459  if( OPT<bool> value = Get<bool>( "input.center_on_zoom" ) )
460  Set( "input.center_on_zoom", !( *value ) );
461 
462  ret &= fromLegacy<int>( aCfg, "OpenGLAntialiasingMode", "graphics.opengl_antialiasing_mode" );
463  ret &= fromLegacy<int>( aCfg, "CairoAntialiasingMode", "graphics.cairo_antialiasing_mode" );
464 
465  ret &= fromLegacy<int>( aCfg, "AutoSaveInterval", "system.autosave_interval" );
466  ret &= fromLegacyString( aCfg, "Editor", "system.editor_name" );
467  ret &= fromLegacy<int>( aCfg, "FileHistorySize", "system.file_history_size" );
468  ret &= fromLegacyString( aCfg, "LanguageID", "system.language" );
469  ret &= fromLegacyString( aCfg, "PdfBrowserName", "system.pdf_viewer_name" );
470  ret &= fromLegacy<bool>( aCfg, "UseSystemBrowser", "system.use_system_pdf_viewer" );
471  ret &= fromLegacyString( aCfg, "WorkingDir", "system.working_dir" );
472 
473  return ret;
474 }
475 
476 
478 {
479  auto addVar =
480  [&]( const wxString& aKey, const wxString& aDefault )
481  {
482  m_Env.vars[aKey] = ENV_VAR_ITEM( aKey, aDefault, aDefault );
483 
484  wxString envValue;
485 
486  if( wxGetEnv( aKey, &envValue ) == true && !envValue.IsEmpty() )
487  {
488  m_Env.vars[aKey].SetValue( envValue );
489  m_Env.vars[aKey].SetDefinedExternally();
490  wxLogTrace( traceEnvVars,
491  "InitializeEnvironment: Entry %s defined externally as %s", aKey,
492  envValue );
493  }
494  else
495  {
496  wxLogTrace( traceEnvVars, "InitializeEnvironment: Setting entry %s to default %s",
497  aKey, aDefault );
498  }
499  };
500 
501  wxFileName basePath( PATHS::GetStockEDALibraryPath(), wxEmptyString );
502 
503  wxFileName path( basePath );
504  path.AppendDir( wxT( "footprints" ) );
505  addVar( wxT( "KICAD6_FOOTPRINT_DIR" ), path.GetFullPath() );
506 
507  path = basePath;
508  path.AppendDir( wxT( "3dmodels" ) );
509  addVar( wxT( "KICAD6_3DMODEL_DIR" ), path.GetFullPath() );
510 
511  // We don't have just one default template path, so use this logic that originally was in
512  // PGM_BASE::InitPgm to determine the best default template path
513  {
514  // Attempt to find the best default template path.
515  SEARCH_STACK bases;
516  SEARCH_STACK templatePaths;
517 
518  SystemDirsAppend( &bases );
519 
520  for( unsigned i = 0; i < bases.GetCount(); ++i )
521  {
522  wxFileName fn( bases[i], wxEmptyString );
523 
524  // Add KiCad template file path to search path list.
525  fn.AppendDir( "template" );
526 
527  // Only add path if exists and can be read by the user.
528  if( fn.DirExists() && fn.IsDirReadable() )
529  {
530  wxLogTrace( tracePathsAndFiles, "Checking template path '%s' exists",
531  fn.GetPath() );
532  templatePaths.AddPaths( fn.GetPath() );
533  }
534  }
535 
536  if( templatePaths.IsEmpty() )
537  {
538  path = basePath;
539  path.AppendDir( "template" );
540  }
541  else
542  {
543  // Take the first one. There may be more but this will likely be the best option.
544  path.AssignDir( templatePaths[0] );
545  }
546 
547  addVar( wxT( "KICAD6_TEMPLATE_DIR" ), path.GetFullPath() );
548  }
549 
550  addVar( wxT( "KICAD_USER_TEMPLATE_DIR" ), PATHS::GetUserTemplatesPath() );
551 
552  addVar( wxT( "KICAD6_3RD_PARTY" ), PATHS::GetDefault3rdPartyPath() );
553 
554  path = basePath;
555  path.AppendDir( wxT( "symbols" ) );
556  addVar( wxT( "KICAD6_SYMBOL_DIR" ), path.GetFullPath() );
557 }
void Set(const std::string &aPath, ValueType aVal)
Stores a value into the JSON document Will throw an exception if ValueType isn't something that the l...
std::vector< PARAM_BASE * > m_params
The list of parameters (owned by this object)
unsigned long long limit_total_size
Maximum total size of backups (bytes), 0 for unlimited.
KiCad uses environment variables internally for determining the base paths for libraries,...
const int commonSchemaVersion
! Update the schema version whenever a migration is required
bool enabled
Automatically back up the project when files are saved.
const wxChar *const tracePathsAndFiles
Flag to enable path and file name debug output.
SETTINGS_LOC
Definition: json_settings.h:46
AUTO_BACKUP m_Backup
System directories search utilities.
APPEARANCE m_Appearance
Stores an enum as an integer.
Definition: parameters.h:215
virtual bool MigrateFromLegacy(wxConfigBase *aLegacyConfig) override
Migrates from wxConfig to JSON-based configuration.
Look for files in a number of paths.
Definition: search_stack.h:41
nlohmann::json json
Definition: gerbview.cpp:41
void InitializeEnvironment()
Creates the built-in environment variables and sets their default values.
std::unique_ptr< JSON_SETTINGS_INTERNALS > m_internals
static wxString GetUserTemplatesPath()
Gets the user path for custom templates.
Definition: paths.cpp:86
void SystemDirsAppend(SEARCH_STACK *aSearchStack)
Append system places to aSearchStack in a platform specific way and pertinent to KiCad programs.
wxLogTrace helper definitions.
ENVIRONMENT m_Env
const wxChar *const traceEnvVars
Flag to enable debug output of environment variable operations.
int min_interval
Minimum time, in seconds, between subsequent backups.
bool fromLegacyString(wxConfigBase *aConfig, const std::string &aKey, const std::string &aDest)
Translates a legacy wxConfig string value to a given JSON pointer value.
const std::set< wxString > envVarBlacklist
! The following environment variables will never be migrated from a previous version
The main config directory (e.g. ~/.config/kicad/)
static wxString GetStockEDALibraryPath()
Gets the stock (install) EDA library data path, which is the base path for templates,...
Definition: paths.cpp:186
boost::optional< T > OPT
Definition: optional.h:7
bool backup_on_autosave
Trigger a backup on autosave.
int limit_daily_files
Maximum files to keep per day, 0 for unlimited.
const wxChar *const traceSettings
Flag to enable debug output of settings operations and management.
static wxString GetDefault3rdPartyPath()
Gets the default path for PCM packages.
Definition: paths.cpp:129
void AddPaths(const wxString &aPaths, int aIndex=-1)
Insert or append path(s).
int limit_total_files
Maximum number of backup archives to retain.