From a4fadfcdf2fdd4044c5ce8d92651f2ad673bce6c Mon Sep 17 00:00:00 2001
From: Jon Evans <jon@craftyjon.com>
Date: Mon, 25 May 2020 16:41:24 -0400
Subject: [PATCH] Begin moving project file load to new system

---
 common/eda_base_frame.cpp             |  2 +
 common/project.cpp                    | 16 ++++-
 common/settings/json_settings.cpp     | 11 ++--
 common/settings/project_file.cpp      | 94 +++++++++++++++++++--------
 common/settings/settings_manager.cpp  | 44 +++++++++++--
 common/wildcards_and_files_ext.cpp    | 14 +++-
 eeschema/files-io.cpp                 |  9 +--
 include/project.h                     | 36 +++++++---
 include/settings/json_settings.h      |  8 +++
 include/settings/project_file.h       |  9 +--
 include/settings/settings_manager.h   | 16 ++++-
 include/wildcards_and_files_ext.h     |  2 +
 kicad/kicad_manager_frame.cpp         | 18 +++--
 kicad/tools/kicad_manager_control.cpp |  6 +-
 pcbnew/files.cpp                      |  5 +-
 qa/eeschema/test_netlists.cpp         |  5 +-
 16 files changed, 217 insertions(+), 78 deletions(-)

diff --git a/common/eda_base_frame.cpp b/common/eda_base_frame.cpp
index b1d9a98cbe..d9a2837a0b 100644
--- a/common/eda_base_frame.cpp
+++ b/common/eda_base_frame.cpp
@@ -87,6 +87,8 @@ EDA_BASE_FRAME::EDA_BASE_FRAME( wxWindow* aParent, FRAME_T aFrameType,
     m_mruPath       = wxStandardPaths::Get().GetDocumentsDir();
     m_FrameSize     = wxSize( s_minsize_x, s_minsize_y );
 
+    m_settingsManager = &Pgm().GetSettingsManager();
+
     // Set a reasonable minimal size for the frame
     SetSizeHints( s_minsize_x, s_minsize_y, -1, -1, -1, -1 );
 
diff --git a/common/project.cpp b/common/project.cpp
index 61f24b6f38..2f3677fcb0 100644
--- a/common/project.cpp
+++ b/common/project.cpp
@@ -36,9 +36,11 @@
 #include <kiway.h>
 #include <kiface_ids.h>
 #include <trace_helpers.h>
+#include <settings/project_file.h>
 
 
-PROJECT::PROJECT()
+PROJECT::PROJECT() :
+        m_projectFile( nullptr )
 {
     memset( m_elems, 0, sizeof(m_elems) );
 }
@@ -73,7 +75,7 @@ bool PROJECT::TextVarResolver( wxString* aToken ) const
 }
 
 
-void PROJECT::SetProjectFullName( const wxString& aFullPathAndName )
+void PROJECT::setProjectFullName( const wxString& aFullPathAndName )
 {
     // Compare paths, rather than inodes, to be less surprising to the user.
     // Create a temporary wxFileName to normalize the path
@@ -92,7 +94,7 @@ void PROJECT::SetProjectFullName( const wxString& aFullPathAndName )
 
         wxASSERT( m_project_name.IsAbsolute() );
 
-        wxASSERT( m_project_name.GetExt() == LegacyProjectFileExtension );
+        wxASSERT( m_project_name.GetExt() == ProjectFileExtension );
 
         // until multiple projects are in play, set an environment variable for the
         // the project pointer.
@@ -333,6 +335,14 @@ wxConfigBase* PROJECT::configCreate( const SEARCH_STACK& aSList,
     wxConfigBase*   cfg = 0;
     wxString        cur_pro_fn = !aProjectFileName ? GetProjectFullName() : aProjectFileName;
 
+    wxFileName filename( cur_pro_fn );
+
+    if( filename.GetExt() == ProjectFileExtension )
+    {
+        filename.SetExt( LegacyProjectFileExtension );
+        cur_pro_fn = filename.GetFullPath();
+    }
+
     // If we do not have a project name or specified name, choose an empty file to store the
     // temporary configuration data in.
     if( cur_pro_fn.IsEmpty() )
diff --git a/common/settings/json_settings.cpp b/common/settings/json_settings.cpp
index 0bd98218d1..5688fe49c3 100644
--- a/common/settings/json_settings.cpp
+++ b/common/settings/json_settings.cpp
@@ -45,6 +45,7 @@ JSON_SETTINGS::JSON_SETTINGS( const std::string& aFilename, SETTINGS_LOC aLocati
         m_createIfMissing( aCreateIfMissing ),
         m_createIfDefault( aCreateIfDefault ),
         m_writeFile( aWriteFile ),
+        m_deleteLegacyAfterMigration( true ),
         m_schemaVersion( aSchemaVersion ),
         m_manager( nullptr )
 {
@@ -121,11 +122,11 @@ bool JSON_SETTINGS::LoadFromFile( const std::string& aDirectory )
     if( aDirectory.empty() )
     {
         path.Assign( m_filename );
-        path.SetExt( wxT( "json" ) );
+        path.SetExt( getFileExt() );
     }
     else
     {
-        path.Assign( aDirectory, m_filename, wxT( "json" ) );
+        path.Assign( aDirectory, m_filename, getFileExt() );
     }
 
     if( !path.Exists() )
@@ -212,7 +213,7 @@ bool JSON_SETTINGS::LoadFromFile( const std::string& aDirectory )
     // If we migrated, clean up the legacy file (with no extension)
     if( legacy_migrated || migrated )
     {
-        if( legacy_migrated && !wxRemoveFile( path.GetFullPath() ) )
+        if( legacy_migrated && m_deleteLegacyAfterMigration && !wxRemoveFile( path.GetFullPath() ) )
         {
             wxLogTrace(
                     traceSettings, "Warning: could not remove legacy file %s", path.GetFullPath() );
@@ -261,11 +262,11 @@ bool JSON_SETTINGS::SaveToFile( const std::string& aDirectory, bool aForce )
     if( aDirectory.empty() )
     {
         path.Assign( m_filename );
-        path.SetExt( wxT( "json" ) );
+        path.SetExt( getFileExt() );
     }
     else
     {
-        path.Assign( aDirectory, m_filename, wxT( "json" ) );
+        path.Assign( aDirectory, m_filename, getFileExt() );
     }
 
     if( !m_createIfMissing && !path.FileExists() )
diff --git a/common/settings/project_file.cpp b/common/settings/project_file.cpp
index 0751d73eb3..75c8bde1a4 100644
--- a/common/settings/project_file.cpp
+++ b/common/settings/project_file.cpp
@@ -34,13 +34,14 @@ const int projectFileSchemaVersion = 1;
 
 PROJECT_FILE::PROJECT_FILE( const std::string& aFullPath ) :
         JSON_SETTINGS( aFullPath, SETTINGS_LOC::NONE, projectFileSchemaVersion ),
-        m_sheets(), m_boards(), m_legacyVars()
+        m_sheets(), m_boards()
 {
+    // Keep old files around
+    m_deleteLegacyAfterMigration = false;
+
     m_params.emplace_back( new PARAM_LIST<FILE_INFO_PAIR>( "sheets", &m_sheets, {} ) );
 
     m_params.emplace_back( new PARAM_LIST<FILE_INFO_PAIR>( "boards", &m_boards, {} ) );
-
-    m_params.emplace_back( new PARAM_MAP<wxString>( "legacy", &m_legacyVars, {} ) );
 }
 
 
@@ -48,7 +49,7 @@ bool PROJECT_FILE::MigrateFromLegacy( wxConfigBase* aLegacyFile )
 {
     bool     ret = true;
     wxString str;
-    long     dummy;
+    long     index = 0;
 
     // Legacy files don't store board info; they assume board matches project name
     // We will leave m_boards empty here so it can be populated with other code
@@ -56,65 +57,102 @@ bool PROJECT_FILE::MigrateFromLegacy( wxConfigBase* aLegacyFile )
     auto loadSheetNames =
             [&]() -> bool
             {
-                int index = 1;
-                wxString entry;
+                int            sheet = 1;
+                wxString       entry;
+                nlohmann::json arr   = nlohmann::json::array();
+
+                wxLogTrace( traceSettings, "Migrating sheet names" );
 
                 aLegacyFile->SetPath( GROUP_SHEET_NAMES );
 
-                while( aLegacyFile->Read( wxString::Format( "%d", index++ ), &entry ) )
+                while( aLegacyFile->Read( wxString::Format( "%d", sheet++ ), &entry ) )
                 {
                     wxArrayString tokens = wxSplit( entry, ':' );
 
                     if( tokens.size() == 2 )
-                        m_sheets.emplace_back( std::make_pair( KIID( tokens[0] ), tokens[1] ) );
+                    {
+                        wxLogTrace( traceSettings, "%d: %s = %s", sheet, tokens[0], tokens[1] );
+                        arr.push_back( nlohmann::json::array( { tokens[0], tokens[1] } ) );
+                    }
                 }
 
+              ( *this )[PointerFromString( "sheets" )] = arr;
+
+                aLegacyFile->SetPath( "/" );
+
                 // TODO: any reason we want to fail on this?
                 return true;
             };
 
     std::vector<wxString> groups;
 
-    bool more = aLegacyFile->GetFirstGroup( str, dummy );
-
-    while( more )
+    while( aLegacyFile->GetNextGroup( str, index ) )
     {
-        if( str == GROUP_SHEET_NAMES )
+        if( str == wxString( GROUP_SHEET_NAMES ).Mid( 1 ) )
             ret |= loadSheetNames();
         else
-            groups.emplace_back( str );
-
-        more = aLegacyFile->GetNextGroup( str, dummy );
+            groups.emplace_back( "/" + str );
     }
 
     auto loadLegacyPairs =
-            [&]( const wxString& aGroup = wxEmptyString ) -> bool
+            [&]( const std::string& aGroup ) -> bool
             {
-                aLegacyFile->SetPath( aGroup );
+                wxLogTrace( traceSettings, "Migrating group %s", aGroup );
+                bool     success = true;
+                wxString keyStr;
+                wxString val;
 
-                bool morePairs = aLegacyFile->GetFirstEntry( str, dummy );
+                index = 0;
 
-                while( morePairs )
+                while( aLegacyFile->GetNextEntry( keyStr, index ) )
                 {
-                    wxString    val = aLegacyFile->Read( str );
-                    std::string key( str.ToUTF8() );
-                    m_legacyVars[key] = val;
-                    morePairs         = aLegacyFile->GetNextEntry( str, dummy );
+                    if( !aLegacyFile->Read( keyStr, &val ) )
+                        continue;
+
+                    std::string key( keyStr.ToUTF8() );
+
+                    wxLogTrace( traceSettings, "    %s = %s", key, val );
+
+                    try
+                    {
+                        nlohmann::json::json_pointer ptr( "/legacy" + aGroup + "/" + key );
+                        ( *this )[ptr] = val;
+                    }
+                    catch( ... )
+                    {
+                        success = false;
+                    }
                 }
 
-                // TODO: any reason we want to fail on this?
-                return true;
+                return success;
             };
 
-    ret &= loadLegacyPairs();
+    ret &= loadLegacyPairs( "" );
 
-    for( const auto& groupName : groups )
-        ret &= loadLegacyPairs( groupName );
+    for( size_t i = 0; i < groups.size(); i++ )
+    {
+        aLegacyFile->SetPath( groups[i] );
+
+        ret &= loadLegacyPairs( groups[i].ToStdString() );
+
+        index = 0;
+
+        while( aLegacyFile->GetNextGroup( str, index ) )
+            groups.emplace_back( groups[i] + "/" + str );
+
+        aLegacyFile->SetPath( "/" );
+    }
 
     return ret;
 }
 
 
+wxString PROJECT_FILE::getFileExt() const
+{
+    return ProjectFileExtension;
+}
+
+
 wxString PROJECT_FILE::getLegacyFileExt() const
 {
     return LegacyProjectFileExtension;
diff --git a/common/settings/settings_manager.cpp b/common/settings/settings_manager.cpp
index 53789402fd..e130e73dca 100644
--- a/common/settings/settings_manager.cpp
+++ b/common/settings/settings_manager.cpp
@@ -35,6 +35,7 @@
 #include <settings/common_settings.h>
 #include <settings/project_file.h>
 #include <settings/settings_manager.h>
+#include <wildcards_and_files_ext.h>
 
 
 /**
@@ -44,6 +45,10 @@
 const char* traceSettings = "SETTINGS";
 
 
+/// Project settings path will be <projectname> + this
+#define PROJECT_SETTINGS_DIR_SUFFIX wxT( "-settings" )
+
+
 SETTINGS_MANAGER::SETTINGS_MANAGER() :
         m_common_settings( nullptr ),
         m_migration_source()
@@ -664,20 +669,28 @@ bool SETTINGS_MANAGER::extractVersion( const std::string& aVersionString, int* a
 
 bool SETTINGS_MANAGER::LoadProject( const wxString& aFullPath )
 {
+    // Normalize path to new format even if migrating from a legacy file
+    wxFileName path( aFullPath );
+
+    if( path.GetExt() == LegacyProjectFileExtension )
+        path.SetExt( ProjectFileExtension );
+
+    wxString fullPath = path.GetFullPath();
+
     // Unload if already loaded
-    if( m_projects.count( aFullPath ) && !UnloadProject( *m_projects.at( aFullPath ).get() ) )
+    if( m_projects.count( fullPath ) && !UnloadProject( *m_projects.at( fullPath ).get() ) )
         return false;
 
     // No MDI yet
     if( !m_projects.empty() && !UnloadProject( *m_projects.begin()->second ) )
         return false;
 
-    wxLogTrace( traceSettings, "Load project %s", aFullPath );
+    wxLogTrace( traceSettings, "Load project %s", fullPath );
 
-    m_projects[aFullPath] = std::make_unique<PROJECT>();
-    m_projects[aFullPath]->SetProjectFullName( aFullPath );
+    m_projects[fullPath] = std::make_unique<PROJECT>();
+    m_projects[fullPath]->setProjectFullName( fullPath );
 
-    return loadProjectFile( *m_projects[aFullPath] );
+    return loadProjectFile( *m_projects[fullPath] );
 }
 
 
@@ -705,6 +718,25 @@ PROJECT& SETTINGS_MANAGER::Prj() const
 }
 
 
+bool SETTINGS_MANAGER::SaveProject()
+{
+    wxString name = Prj().GetProjectFullName();
+
+    if( !m_project_files.count( name ) )
+        return false;
+
+    m_project_files[name]->SaveToFile();
+
+    return true;
+}
+
+
+wxString SETTINGS_MANAGER::GetProjectSettingsPath() const
+{
+    return Prj().GetProjectPath() + Prj().GetProjectName() + PROJECT_SETTINGS_DIR_SUFFIX;
+}
+
+
 bool SETTINGS_MANAGER::loadProjectFile( PROJECT& aProject )
 {
     std::string fn( aProject.GetProjectFullName().ToUTF8() );
@@ -714,6 +746,8 @@ bool SETTINGS_MANAGER::loadProjectFile( PROJECT& aProject )
 
     m_project_files[aProject.GetProjectFullName()] = file;
 
+    aProject.setProjectFile( file );
+
     return file->LoadFromFile();
 }
 
diff --git a/common/wildcards_and_files_ext.cpp b/common/wildcards_and_files_ext.cpp
index e7d34a4e91..3731dcab68 100644
--- a/common/wildcards_and_files_ext.cpp
+++ b/common/wildcards_and_files_ext.cpp
@@ -189,7 +189,19 @@ wxString LegacySymbolLibFileWildcard()
 
 wxString ProjectFileWildcard()
 {
-    return _( "KiCad project files" ) + AddFileExtListToFilter( { "pro" } );
+    return _( "KiCad project files" ) + AddFileExtListToFilter( { "kicad_pro" } );
+}
+
+
+wxString LegacyProjectFileWildcard()
+{
+    return _( "KiCad legacy project files" ) + AddFileExtListToFilter( { "pro" } );
+}
+
+
+wxString AllProjectFilesWildcard()
+{
+    return _( "All KiCad project files" ) + AddFileExtListToFilter( { "kicad_pro", "pro" } );
 }
 
 
diff --git a/eeschema/files-io.cpp b/eeschema/files-io.cpp
index 1d2763736e..d7b1602ce1 100644
--- a/eeschema/files-io.cpp
+++ b/eeschema/files-io.cpp
@@ -54,6 +54,7 @@
 #include <connection_graph.h>
 #include <tool/actions.h>
 #include <tools/sch_editor_control.h>
+#include <settings/settings_manager.h>
 #include <netlist.h>
 #include <widgets/infobar.h>
 
@@ -303,7 +304,7 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
         if( pro.GetFullPath() != Prj().GetProjectFullName()
           || !Prj().GetElem( PROJECT::ELEM_SCH_PART_LIBS ) )
         {
-            Prj().SetProjectFullName( pro.GetFullPath() );
+            GetSettingsManager()->LoadProject( pro.GetFullPath() );
 
             // load the libraries here, not in SCH_SCREEN::Draw() which is a context
             // that will not tolerate DisplayError() dialog since we're already in an
@@ -322,7 +323,7 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
 
     // Make sure the project file name is set (it won't be in standalone mode)
     if( pro.GetFullPath() != Prj().GetProjectFullName() )
-        Prj().SetProjectFullName( pro.GetFullPath() );
+        GetSettingsManager()->LoadProject( pro.GetFullPath() );
 
     LoadProjectFile();
 
@@ -605,8 +606,8 @@ void SCH_EDIT_FRAME::OnImportProject( wxCommandEvent& aEvent )
     if( setProject )
     {
         wxFileName projectFn( dlg.GetPath() );
-        projectFn.SetExt( LegacyProjectFileExtension );
-        Prj().SetProjectFullName( projectFn.GetFullPath() );
+        projectFn.SetExt( ProjectFileExtension );
+        GetSettingsManager()->LoadProject( projectFn.GetFullPath() );
     }
 
     // For now there is only one import plugin
diff --git a/include/project.h b/include/project.h
index 2830a95c6b..21b9b5e5fb 100644
--- a/include/project.h
+++ b/include/project.h
@@ -47,6 +47,7 @@ class S3D_CACHE;
 class KIWAY;
 class SYMBOL_LIB_TABLE;
 class FILENAME_RESOLVER;
+class PROJECT_FILE;
 
 #define VTBL_ENTRY      virtual
 
@@ -58,6 +59,9 @@ class FILENAME_RESOLVER;
  */
 class PROJECT
 {
+    friend class SETTINGS_MANAGER; // so that SM can set project path
+    friend class TEST_NETLISTS_FIXTURE; // TODO(JE) make this not required
+
 public:
 
     /// A PROJECT can hold stuff it knows nothing about, in the form of
@@ -84,16 +88,6 @@ public:
 
     // VTBL_ENTRY bool MaybeLoadProjectSettings( const std::vector<wxString>& aFileSet );
 
-    /**
-     * Function SetProjectFullName
-     * sets the:
-     * 1) full directory, 2) basename, and 3) extension of the project.  This is
-     * the name of the *.pro file with full absolute path and it also defines
-     * the name of the project.  The project name and the *.pro file names are
-     * exactly the same, providing the *.pro filename is absolute.
-     */
-    VTBL_ENTRY void SetProjectFullName( const wxString& aFullPathAndName );
-
     /**
      * Function GetProjectFullName
      * returns the full path and name of the project.  This is the same as the
@@ -321,6 +315,25 @@ public:
 
 private:
 
+    /**
+     * Sets the:
+     * 1) full directory, 2) basename, and 3) extension of the project.  This is
+     * the name of the *.pro file with full absolute path and it also defines
+     * the name of the project.  The project name and the *.pro file names are
+     * exactly the same, providing the *.pro filename is absolute.
+     */
+    VTBL_ENTRY void setProjectFullName( const wxString& aFullPathAndName );
+
+    /**
+     * Sets the backing store file for this project
+     * Should only be called by SETTINGS_MANGER on load.
+     * @param aFile is a loaded PROJECT_FILE
+     */
+    VTBL_ENTRY void setProjectFile( PROJECT_FILE* aFile )
+    {
+        m_projectFile = aFile;
+    }
+
     /**
      * Function configCreate
      * loads a *.pro file and returns a wxConfigBase.
@@ -340,6 +353,9 @@ private:
     wxFileName      m_project_name;         ///< \<fullpath\>/\<basename\>.pro
     wxString        m_pro_date_and_time;
 
+    /// Backing store for project data -- owned by SETTINGS_MANAGER
+    PROJECT_FILE*   m_projectFile;
+
     std::map<KIID, wxString>     m_sheetNames;
     std::map<wxString, wxString> m_textVars;
 
diff --git a/include/settings/json_settings.h b/include/settings/json_settings.h
index 172ba736c7..b74dae95b9 100644
--- a/include/settings/json_settings.h
+++ b/include/settings/json_settings.h
@@ -213,6 +213,11 @@ protected:
     bool fromLegacyColor( wxConfigBase* aConfig, const std::string& aKey,
                           const std::string& aDest );
 
+    virtual wxString getFileExt() const
+    {
+        return wxT( "json" );
+    }
+
     virtual wxString getLegacyFileExt() const
     {
         return wxEmptyString;
@@ -245,6 +250,9 @@ protected:
     /// Whether or not the backing store file should be written
     bool m_writeFile;
 
+    /// Whether or not to delete legacy file after migration
+    bool m_deleteLegacyAfterMigration;
+
     /// Version of this settings schema.
     int m_schemaVersion;
 
diff --git a/include/settings/project_file.h b/include/settings/project_file.h
index 8e349ed6bf..70dbc1ffd0 100644
--- a/include/settings/project_file.h
+++ b/include/settings/project_file.h
@@ -52,6 +52,8 @@ public:
     virtual bool MigrateFromLegacy( wxConfigBase* aLegacyFile ) override;
 
 protected:
+    wxString getFileExt() const override;
+
     wxString getLegacyFileExt() const override;
 
 private:
@@ -61,13 +63,6 @@ private:
 
     /// A list of board files in this project
     std::vector<FILE_INFO_PAIR> m_boards;
-
-    /**
-     * Stores all K/V pairs migrated from a legacy (.pro) file so that the legacy file can be
-     * removed before these settings are migrated in by PROJECT_SETTINGS objects later.
-     */
-    std::map<std::string, wxString> m_legacyVars;
-
 };
 
 // Specializations to allow directly reading/writing FILE_INFO_PAIRs from JSON
diff --git a/include/settings/settings_manager.h b/include/settings/settings_manager.h
index b1a2646a95..73213fe85c 100644
--- a/include/settings/settings_manager.h
+++ b/include/settings/settings_manager.h
@@ -182,9 +182,9 @@ public:
     void ReloadColorSettings();
 
     /**
-     * Loads a project
+     * Loads a project or sets up a new project with a specified path
      * @param aFullPath is the full path to the project
-     * @return true if the PROJECT_FILE was successfully loaded
+     * @return true if the PROJECT_FILE was successfully loaded from disk
      */
     bool LoadProject( const wxString& aFullPath );
 
@@ -201,6 +201,18 @@ public:
      */
     PROJECT& Prj() const;
 
+    /**
+     * Saves the one and only project
+     * TODO: Update for MDI
+     * @return true if save was successful
+     */
+    bool SaveProject();
+
+    /**
+     * @return the path to the settings folder for the loaded project
+     */
+    wxString GetProjectSettingsPath() const;
+
     /**
      * Checks if a given path is probably a valid KiCad configuration directory.
      * Actually it just checks if a file called "kicad_common" exists, because that's probably
diff --git a/include/wildcards_and_files_ext.h b/include/wildcards_and_files_ext.h
index 88d189537f..e7cb974511 100644
--- a/include/wildcards_and_files_ext.h
+++ b/include/wildcards_and_files_ext.h
@@ -176,6 +176,8 @@ extern wxString SchematicSymbolFileWildcard();
 extern wxString KiCadSymbolLibFileWildcard();
 extern wxString LegacySymbolLibFileWildcard();
 extern wxString ProjectFileWildcard();
+extern wxString LegacyProjectFileWildcard();
+extern wxString AllProjectFilesWildcard();
 extern wxString KiCadSchematicFileWildcard();
 extern wxString LegacySchematicFileWildcard();
 extern wxString BoardFileWildcard();
diff --git a/kicad/kicad_manager_frame.cpp b/kicad/kicad_manager_frame.cpp
index d821996805..2b433f430e 100644
--- a/kicad/kicad_manager_frame.cpp
+++ b/kicad/kicad_manager_frame.cpp
@@ -37,6 +37,7 @@
 #include <launch_ext.h>
 #include <panel_hotkeys_editor.h>
 #include <settings/common_settings.h>
+#include <settings/settings_manager.h>
 #include <tool/action_toolbar.h>
 #include <tool/common_control.h>
 #include <tool/tool_manager.h>
@@ -203,8 +204,6 @@ void KICAD_MANAGER_FRAME::SetProjectFileName( const wxString& aFullProjectProFil
     if( !fn.IsAbsolute() )
         fn.MakeAbsolute();
 
-    Prj().SetProjectFullName( fn.GetFullPath() );
-
     SetTitle( wxString( "KiCad " ) + GetBuildVersion() );
     wxString title = GetTitle() + " " + fn.GetFullPath();
 
@@ -343,12 +342,13 @@ void KICAD_MANAGER_FRAME::LoadProject( const wxFileName& aProjectFileName )
 
     // Save the project file for the currently loaded project.
     if( m_active_project )
-        Prj().ConfigLoad( PgmTop().SysSearch(), GeneralGroupName, s_KicadManagerParams );
+        Pgm().GetSettingsManager().SaveProject();
 
     m_active_project = true;
     ClearMsg();
-    SetProjectFileName( aProjectFileName.GetFullPath() );
-    Prj().ConfigLoad( PgmTop().SysSearch(), GeneralGroupName, s_KicadManagerParams );
+
+    Pgm().GetSettingsManager().LoadProject( aProjectFileName.GetFullPath() );
+    SetProjectFileName( Prj().GetProjectFullName() );
 
     if( aProjectFileName.IsDirWritable() )
         SetMruPath( Prj().GetProjectPath() ); // Only set MRU path if we have write access. Why?
@@ -381,13 +381,17 @@ void KICAD_MANAGER_FRAME::CreateNewProject( const wxFileName& aProjectFileName )
     // Copy kicad.pro file from template folder.
     if( !aProjectFileName.FileExists() )
     {
+        // TODO(JE) provide in new format
         wxString srcFileName = sys_search().FindValidPath( "kicad.pro" );
 
+        wxFileName destFileName( aProjectFileName );
+        destFileName.SetExt( LegacyProjectFileExtension );
+
         // Create a minimal project (.pro) file if the template project file could not be copied.
         if( !wxFileName::FileExists( srcFileName )
-            || !wxCopyFile( srcFileName, aProjectFileName.GetFullPath() ) )
+            || !wxCopyFile( srcFileName, destFileName.GetFullPath() ) )
         {
-            Prj().ConfigSave( PgmTop().SysSearch(), GeneralGroupName, s_KicadManagerParams );
+            Pgm().GetSettingsManager().SaveProject();
         }
     }
 
diff --git a/kicad/tools/kicad_manager_control.cpp b/kicad/tools/kicad_manager_control.cpp
index 4b4113ab2b..3426a0e4d3 100644
--- a/kicad/tools/kicad_manager_control.cpp
+++ b/kicad/tools/kicad_manager_control.cpp
@@ -292,15 +292,17 @@ int KICAD_MANAGER_CONTROL::NewFromTemplate( const TOOL_EVENT& aEvent )
 
 int KICAD_MANAGER_CONTROL::OpenProject( const TOOL_EVENT& aEvent )
 {
+    wxString wildcard = ProjectFileWildcard() + "|" + LegacyProjectFileWildcard() + "|"
+                        + AllProjectFilesWildcard();
+
     wxString     default_dir = m_frame->GetMruPath();
     wxFileDialog dlg( m_frame, _( "Open Existing Project" ), default_dir, wxEmptyString,
-                      ProjectFileWildcard(), wxFD_OPEN | wxFD_FILE_MUST_EXIST );
+                      wildcard, wxFD_OPEN | wxFD_FILE_MUST_EXIST );
 
     if( dlg.ShowModal() == wxID_CANCEL )
         return -1;
 
     wxFileName pro( dlg.GetPath() );
-    pro.SetExt( LegacyProjectFileExtension );     // enforce extension
 
     if( !pro.IsAbsolute() )
         pro.MakeAbsolute();
diff --git a/pcbnew/files.cpp b/pcbnew/files.cpp
index 08b800e2d1..b33b49c05a 100644
--- a/pcbnew/files.cpp
+++ b/pcbnew/files.cpp
@@ -51,6 +51,7 @@
 #include <ratsnest/ratsnest_data.h>
 
 #include <wx/wupdlock.h>
+#include <settings/settings_manager.h>
 
 
 //#define     USE_INSTRUMENTATION     1
@@ -310,7 +311,7 @@ bool PCB_EDIT_FRAME::Files_io_from_id( int id )
         wxFileName fn( wxStandardPaths::Get().GetDocumentsDir(), wxT( "noname" ),
                        LegacyProjectFileExtension );
 
-        Prj().SetProjectFullName( fn.GetFullPath() );
+        GetSettingsManager()->LoadProject( fn.GetFullPath() );
 
         onBoardLoaded();
 
@@ -507,7 +508,7 @@ bool PCB_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
         // The calling code should know not to ask me here to change projects unless
         // it knows what consequences that will have on other KIFACEs running and using
         // this same PROJECT.  It can be very harmful if that calling code is stupid.
-        Prj().SetProjectFullName( pro.GetFullPath() );
+        GetSettingsManager()->LoadProject( pro.GetFullPath() );
 
         // load project settings before BOARD
         LoadProjectSettings();
diff --git a/qa/eeschema/test_netlists.cpp b/qa/eeschema/test_netlists.cpp
index 2cad8aa444..a47abd8783 100644
--- a/qa/eeschema/test_netlists.cpp
+++ b/qa/eeschema/test_netlists.cpp
@@ -81,9 +81,10 @@ void TEST_NETLISTS_FIXTURE::loadSchematic( const wxString& aBaseName )
     BOOST_TEST_MESSAGE( fn );
 
     wxFileName pro( fn );
-    pro.SetExt( LegacyProjectFileExtension );
+    pro.SetExt( ProjectFileExtension );
 
-    m_project.SetProjectFullName( pro.GetFullPath() );
+    // TODO(JE) Make this not required
+    m_project.setProjectFullName( pro.GetFullPath() );
     m_project.SetElem( PROJECT::ELEM_SCH_PART_LIBS, nullptr );
 
     m_schematic.Reset();