From 9342aea7fa7ffb3fe1526756e3810da06bca5349 Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Wed, 3 Sep 2025 15:51:04 -0700 Subject: [PATCH] Move new project to templates If the baseline default project doesn't exist on disk, create it and use that as an empty project. Allows the designer to modify the default new project used or select from existing templates --- kicad/dialogs/dialog_template_selector.cpp | 30 +- kicad/dialogs/dialog_template_selector.h | 6 +- kicad/menubar.cpp | 1 - kicad/tools/kicad_manager_actions.cpp | 9 - kicad/tools/kicad_manager_actions.h | 1 - kicad/tools/kicad_manager_control.cpp | 327 ++++++++++++--------- kicad/tools/kicad_manager_control.h | 1 - 7 files changed, 213 insertions(+), 162 deletions(-) diff --git a/kicad/dialogs/dialog_template_selector.cpp b/kicad/dialogs/dialog_template_selector.cpp index 7a0ad32382..d1b111a80e 100644 --- a/kicad/dialogs/dialog_template_selector.cpp +++ b/kicad/dialogs/dialog_template_selector.cpp @@ -229,7 +229,8 @@ void DIALOG_TEMPLATE_SELECTOR::OnPageChange( wxNotebookEvent& event ) DIALOG_TEMPLATE_SELECTOR::DIALOG_TEMPLATE_SELECTOR( wxWindow* aParent, const wxPoint& aPos, const wxSize& aSize, - std::map aTitleDirMap ) : + std::map aTitleDirMap, + const wxFileName& aDefaultTemplate ) : DIALOG_TEMPLATE_SELECTOR_BASE( aParent, wxID_ANY, _( "Project Template Selector" ), aPos, aSize ) { @@ -237,6 +238,8 @@ DIALOG_TEMPLATE_SELECTOR::DIALOG_TEMPLATE_SELECTOR( wxWindow* aParent, const wxP m_reloadButton->SetBitmap( KiBitmapBundle( BITMAPS::small_refresh ) ); m_selectedWidget = nullptr; + m_defaultTemplatePath = aDefaultTemplate; + m_defaultWidget = nullptr; for( auto& [title, pathFname] : aTitleDirMap ) { @@ -273,17 +276,30 @@ DIALOG_TEMPLATE_SELECTOR::DIALOG_TEMPLATE_SELECTOR( wxWindow* aParent, const wxP m_panels[0]->Layout(); } + if( m_defaultWidget ) + m_defaultWidget->Select(); + // Set welcome HTML after dialog is fully constructed CallAfter( [this]() { #if defined (_WIN32) - // For some reason the next calls need it to work fine on Windows, especially with MSYS2 wxSafeYield(); - // Deselect the m_tcTemplatePath string (selected for some strange reason) m_tcTemplatePath->SelectNone(); #endif - m_webviewPanel->SetPage( GetWelcomeHtml() ); + if( m_selectedWidget ) + { + wxFileName htmlFile = m_selectedWidget->GetTemplate()->GetHtmlFile(); + + if( htmlFile.FileExists() && htmlFile.IsFileReadable() ) + m_webviewPanel->LoadURL( wxFileName::FileNameToURL( htmlFile ) ); + else + m_webviewPanel->SetPage( GetWelcomeHtml() ); + } + else + { + m_webviewPanel->SetPage( GetWelcomeHtml() ); + } }); // When all widgets have the size fixed, call finishDialogSettings to update sizers @@ -329,6 +345,12 @@ void DIALOG_TEMPLATE_SELECTOR::AddTemplate( int aPage, PROJECT_TEMPLATE* aTempla TEMPLATE_WIDGET* w = new TEMPLATE_WIDGET( m_panels[aPage]->m_scrolledWindow, this ); w->SetTemplate( aTemplate ); m_panels[aPage]->AddTemplateWidget( w ); + + wxFileName base = aTemplate->GetHtmlFile(); + base.RemoveLastDir(); + + if( m_defaultTemplatePath.IsOk() && base == m_defaultTemplatePath ) + m_defaultWidget = w; } diff --git a/kicad/dialogs/dialog_template_selector.h b/kicad/dialogs/dialog_template_selector.h index 09950c9b9e..b4d93c3b42 100644 --- a/kicad/dialogs/dialog_template_selector.h +++ b/kicad/dialogs/dialog_template_selector.h @@ -30,6 +30,7 @@ #include "project_template.h" #include +#include class DIALOG_TEMPLATE_SELECTOR; @@ -90,7 +91,8 @@ class DIALOG_TEMPLATE_SELECTOR : public DIALOG_TEMPLATE_SELECTOR_BASE { public: DIALOG_TEMPLATE_SELECTOR( wxWindow* aParent, const wxPoint& aPos, const wxSize& aSize, - std::map aTitleDirMap ); + std::map aTitleDirMap, + const wxFileName& aDefaultTemplate ); /** * @return the selected template, or NULL @@ -119,6 +121,8 @@ private: protected: std::vector m_panels; TEMPLATE_WIDGET* m_selectedWidget; + wxFileName m_defaultTemplatePath; + TEMPLATE_WIDGET* m_defaultWidget; }; #endif diff --git a/kicad/menubar.cpp b/kicad/menubar.cpp index 253fc19df6..be3b71a44e 100644 --- a/kicad/menubar.cpp +++ b/kicad/menubar.cpp @@ -75,7 +75,6 @@ void KICAD_MANAGER_FRAME::doReCreateMenuBar() openRecentMenu->SetTitle( _( "Open Recent" ) ); fileMenu->Add( KICAD_MANAGER_ACTIONS::newProject ); - fileMenu->Add( KICAD_MANAGER_ACTIONS::newFromTemplate ); if( Pgm().GetCommonSettings() && Pgm().GetCommonSettings()->m_Git.enableGit ) fileMenu->Add( KICAD_MANAGER_ACTIONS::newFromRepository ); diff --git a/kicad/tools/kicad_manager_actions.cpp b/kicad/tools/kicad_manager_actions.cpp index 4dbffdd50d..d4f2f2e5cb 100644 --- a/kicad/tools/kicad_manager_actions.cpp +++ b/kicad/tools/kicad_manager_actions.cpp @@ -41,15 +41,6 @@ TOOL_ACTION KICAD_MANAGER_ACTIONS::newProject( TOOL_ACTION_ARGS() .DefaultHotkey( MD_CTRL + 'N' ) .LegacyHotkeyName( "New Project" ) .FriendlyName( _( "New Project..." ) ) - .Tooltip( _( "Create a new, blank project" ) ) - .Icon( BITMAPS::new_project ) ); - -TOOL_ACTION KICAD_MANAGER_ACTIONS::newFromTemplate( TOOL_ACTION_ARGS() - .Name( "kicad.Control.newFromTemplate" ) - .Scope( AS_GLOBAL ) - .DefaultHotkey( MD_CTRL + 'T' ) - .LegacyHotkeyName( "New Project From Template" ) - .FriendlyName( _( "New Project from Template..." ) ) .Tooltip( _( "Create a new project based on an existing project" ) ) .Icon( BITMAPS::new_project_from_template ) ); diff --git a/kicad/tools/kicad_manager_actions.h b/kicad/tools/kicad_manager_actions.h index 2683eee962..f13db64d73 100644 --- a/kicad/tools/kicad_manager_actions.h +++ b/kicad/tools/kicad_manager_actions.h @@ -32,7 +32,6 @@ class KICAD_MANAGER_ACTIONS : public ACTIONS { public: static TOOL_ACTION newProject; - static TOOL_ACTION newFromTemplate; static TOOL_ACTION newFromRepository; static TOOL_ACTION newJobsetFile; static TOOL_ACTION openDemoProject; diff --git a/kicad/tools/kicad_manager_control.cpp b/kicad/tools/kicad_manager_control.cpp index 82874d0412..3181a6adc2 100644 --- a/kicad/tools/kicad_manager_control.cpp +++ b/kicad/tools/kicad_manager_control.cpp @@ -48,6 +48,7 @@ #include #include #include +#include #include #include "dialog_pcm.h" #include @@ -138,16 +139,193 @@ wxFileName KICAD_MANAGER_CONTROL::newProjectDirectory( wxString* aFileName, bool } +static wxFileName ensureDefaultProjectTemplate() +{ + ENV_VAR_MAP_CITER it = Pgm().GetLocalEnvVariables().find( "KICAD_USER_TEMPLATE_DIR" ); + + if( it == Pgm().GetLocalEnvVariables().end() || it->second.GetValue() == wxEmptyString ) + return wxFileName(); + + wxFileName templatePath; + templatePath.AssignDir( it->second.GetValue() ); + templatePath.AppendDir( "default" ); + + if( templatePath.DirExists() ) + return templatePath; + + if( !templatePath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) ) + return wxFileName(); + + wxFileName metaDir = templatePath; + metaDir.AppendDir( METADIR ); + + if( !metaDir.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) ) + return wxFileName(); + + wxFileName infoFile = metaDir; + infoFile.SetFullName( METAFILE_INFO_HTML ); + wxFFile info( infoFile.GetFullPath(), wxT( "w" ) ); + + if( !info.IsOpened() ) + return wxFileName(); + + info.Write( wxT( "Default" ) ); + info.Close(); + + wxFileName proFile = templatePath; + proFile.SetFullName( wxT( "default.kicad_pro" ) ); + wxFFile proj( proFile.GetFullPath(), wxT( "w" ) ); + + if( !proj.IsOpened() ) + return wxFileName(); + + proj.Write( wxT( "{}" ) ); + proj.Close(); + + return templatePath; +} + int KICAD_MANAGER_CONTROL::NewProject( const TOOL_EVENT& aEvent ) { - wxFileName pro = newProjectDirectory(); + wxFileName defaultTemplate = ensureDefaultProjectTemplate(); - if( !pro.IsOk() ) + if( !defaultTemplate.IsOk() ) + { + wxFileName pro = newProjectDirectory(); + + if( !pro.IsOk() ) + return -1; + + m_frame->CreateNewProject( pro ); + m_frame->LoadProject( pro ); + + return 0; + } + + KICAD_SETTINGS* settings = GetAppSettings( "kicad" ); + std::map titleDirMap; + wxFileName templatePath; + + std::optional v = ENV_VAR::GetVersionedEnvVarValue( Pgm().GetLocalEnvVariables(), + wxT( "TEMPLATE_DIR" ) ); + + if( v && !v->IsEmpty() ) + { + templatePath.AssignDir( *v ); + titleDirMap.emplace( _( "System Templates" ), templatePath ); + } + + ENV_VAR_MAP_CITER itUser = Pgm().GetLocalEnvVariables().find( "KICAD_USER_TEMPLATE_DIR" ); + + if( itUser != Pgm().GetLocalEnvVariables().end() && itUser->second.GetValue() != wxEmptyString ) + { + templatePath.AssignDir( itUser->second.GetValue() ); + titleDirMap.emplace( _( "User Templates" ), templatePath ); + } + + DIALOG_TEMPLATE_SELECTOR ps( m_frame, settings->m_TemplateWindowPos, settings->m_TemplateWindowSize, + titleDirMap, defaultTemplate ); + + int result = ps.ShowModal(); + + settings->m_TemplateWindowPos = ps.GetPosition(); + settings->m_TemplateWindowSize = ps.GetSize(); + + if( result != wxID_OK ) return -1; - m_frame->CreateNewProject( pro ); - m_frame->LoadProject( pro ); + if( !ps.GetSelectedTemplate() ) + { + wxMessageBox( _( "No project template was selected. Cannot generate new project." ), _( "Error" ), + wxOK | wxICON_ERROR, m_frame ); + return -1; + } + + wxString default_dir = wxFileName( Prj().GetProjectFullName() ).GetPathWithSep(); + wxString title = _( "New Project Folder" ); + wxFileDialog dlg( m_frame, title, default_dir, wxEmptyString, FILEEXT::ProjectFileWildcard(), + wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); + + dlg.AddShortcut( PATHS::GetDefaultUserProjectsPath() ); + + FILEDLG_NEW_PROJECT newProjectHook; + dlg.SetCustomizeHook( newProjectHook ); + + if( dlg.ShowModal() == wxID_CANCEL ) + return -1; + + wxFileName fn( dlg.GetPath() ); + + if( !fn.GetExt().IsEmpty() && fn.GetExt().ToStdString() != FILEEXT::ProjectFileExtension ) + fn.SetName( fn.GetName() + wxT( "." ) + fn.GetExt() ); + + fn.SetExt( FILEEXT::ProjectFileExtension ); + + if( !fn.IsAbsolute() ) + fn.MakeAbsolute(); + + bool createNewDir = false; + createNewDir = newProjectHook.GetCreateNewDir(); + + if( createNewDir ) + fn.AppendDir( fn.GetName() ); + + if( !fn.DirExists() && !fn.Mkdir() ) + { + DisplayErrorMessage( m_frame, wxString::Format( _( "Folder '%s' could not be created.\n\n" + "Make sure you have write permissions and try again." ), + fn.GetPath() ) ); + return -1; + } + + if( !fn.IsDirWritable() ) + { + DisplayErrorMessage( m_frame, wxString::Format( _( "Insufficient permissions to write to folder '%s'." ), + fn.GetPath() ) ); + return -1; + } + + std::vector< wxFileName > destFiles; + + if( ps.GetSelectedTemplate()->GetDestinationFiles( fn, destFiles ) ) + { + std::vector overwrittenFiles; + + for( const wxFileName& file : destFiles ) + { + if( file.FileExists() ) + overwrittenFiles.push_back( file ); + } + + if( !overwrittenFiles.empty() ) + { + wxString extendedMsg = _( "Overwriting files:" ) + "\n"; + + for( const wxFileName& file : overwrittenFiles ) + extendedMsg += "\n" + file.GetFullName(); + + KIDIALOG msgDlg( m_frame, _( "Similar files already exist in the destination folder." ), + _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING ); + msgDlg.SetExtendedMessage( extendedMsg ); + msgDlg.SetOKLabel( _( "Overwrite" ) ); + msgDlg.DoNotShowCheckbox( __FILE__, __LINE__ ); + + if( msgDlg.ShowModal() == wxID_CANCEL ) + return -1; + } + } + + wxString errorMsg; + + if( !ps.GetSelectedTemplate()->CreateProject( fn, &errorMsg ) ) + { + DisplayErrorMessage( m_frame, _( "A problem occurred creating new project from template." ), errorMsg ); + return -1; + } + + m_frame->CreateNewProject( fn.GetFullPath() ); + m_frame->LoadProject( fn ); return 0; } @@ -246,146 +424,6 @@ int KICAD_MANAGER_CONTROL::NewJobsetFile( const TOOL_EVENT& aEvent ) } -int KICAD_MANAGER_CONTROL::NewFromTemplate( const TOOL_EVENT& aEvent ) -{ - KICAD_SETTINGS* settings = GetAppSettings( "kicad" ); - std::map titleDirMap; - wxFileName templatePath; - - // KiCad system template path. - std::optional v = ENV_VAR::GetVersionedEnvVarValue( Pgm().GetLocalEnvVariables(), - wxT( "TEMPLATE_DIR" ) ); - - if( v && !v->IsEmpty() ) - { - templatePath.AssignDir( *v ); - titleDirMap.emplace( _( "System Templates" ), templatePath ); - } - - // User template path. - ENV_VAR_MAP_CITER it = Pgm().GetLocalEnvVariables().find( "KICAD_USER_TEMPLATE_DIR" ); - - if( it != Pgm().GetLocalEnvVariables().end() && it->second.GetValue() != wxEmptyString ) - { - templatePath.AssignDir( it->second.GetValue() ); - titleDirMap.emplace( _( "User Templates" ), templatePath ); - } - - DIALOG_TEMPLATE_SELECTOR ps( m_frame, settings->m_TemplateWindowPos, settings->m_TemplateWindowSize, - titleDirMap ); - - // Show the project template selector dialog - int result = ps.ShowModal(); - - settings->m_TemplateWindowPos = ps.GetPosition(); - settings->m_TemplateWindowSize = ps.GetSize(); - - if( result != wxID_OK ) - return -1; - - if( !ps.GetSelectedTemplate() ) - { - wxMessageBox( _( "No project template was selected. Cannot generate new project." ), _( "Error" ), - wxOK | wxICON_ERROR, m_frame ); - - return -1; - } - - // Get project destination folder and project file name. - wxString default_dir = wxFileName( Prj().GetProjectFullName() ).GetPathWithSep(); - wxString title = _( "New Project Folder" ); - wxFileDialog dlg( m_frame, title, default_dir, wxEmptyString, FILEEXT::ProjectFileWildcard(), - wxFD_SAVE | wxFD_OVERWRITE_PROMPT ); - - dlg.AddShortcut( PATHS::GetDefaultUserProjectsPath() ); - - // Add a "Create a new directory" checkbox - FILEDLG_NEW_PROJECT newProjectHook; - dlg.SetCustomizeHook( newProjectHook ); - - if( dlg.ShowModal() == wxID_CANCEL ) - return -1; - - wxFileName fn( dlg.GetPath() ); - - // wxFileName automatically extracts an extension. But if it isn't a .kicad_pro extension, - // we should keep it as part of the filename - if( !fn.GetExt().IsEmpty() && fn.GetExt().ToStdString() != FILEEXT::ProjectFileExtension ) - fn.SetName( fn.GetName() + wxT( "." ) + fn.GetExt() ); - - fn.SetExt( FILEEXT::ProjectFileExtension ); - - if( !fn.IsAbsolute() ) - fn.MakeAbsolute(); - - bool createNewDir = false; - createNewDir = newProjectHook.GetCreateNewDir(); - - // Append a new directory with the same name of the project file. - if( createNewDir ) - fn.AppendDir( fn.GetName() ); - - // Check if the project directory is empty if it already exists. - if( !fn.DirExists() && !fn.Mkdir() ) - { - DisplayErrorMessage( m_frame, wxString::Format( _( "Folder '%s' could not be created.\n\n" - "Make sure you have write permissions and try again." ), - fn.GetPath() ) ); - return -1; - } - - if( !fn.IsDirWritable() ) - { - DisplayErrorMessage( m_frame, wxString::Format( _( "Insufficient permissions to write to folder '%s'." ), - fn.GetPath() ) ); - return -1; - } - - // Make sure we are not overwriting anything in the destination folder. - std::vector< wxFileName > destFiles; - - if( ps.GetSelectedTemplate()->GetDestinationFiles( fn, destFiles ) ) - { - std::vector overwrittenFiles; - - for( const wxFileName& file : destFiles ) - { - if( file.FileExists() ) - overwrittenFiles.push_back( file ); - } - - if( !overwrittenFiles.empty() ) - { - wxString extendedMsg = _( "Overwriting files:" ) + "\n"; - - for( const wxFileName& file : overwrittenFiles ) - extendedMsg += "\n" + file.GetFullName(); - - KIDIALOG msgDlg( m_frame, _( "Similar files already exist in the destination folder." ), - _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING ); - msgDlg.SetExtendedMessage( extendedMsg ); - msgDlg.SetOKLabel( _( "Overwrite" ) ); - msgDlg.DoNotShowCheckbox( __FILE__, __LINE__ ); - - if( msgDlg.ShowModal() == wxID_CANCEL ) - return -1; - } - } - - wxString errorMsg; - - // The selected template widget contains the template we're attempting to use to - // create a project - if( !ps.GetSelectedTemplate()->CreateProject( fn, &errorMsg ) ) - { - DisplayErrorMessage( m_frame, _( "A problem occurred creating new project from template." ), errorMsg ); - return -1; - } - - m_frame->CreateNewProject( fn.GetFullPath() ); - m_frame->LoadProject( fn ); - return 0; -} int KICAD_MANAGER_CONTROL::openProject( const wxString& aDefaultDir ) @@ -1021,7 +1059,6 @@ int KICAD_MANAGER_CONTROL::ShowPluginManager( const TOOL_EVENT& aEvent ) void KICAD_MANAGER_CONTROL::setTransitions() { Go( &KICAD_MANAGER_CONTROL::NewProject, KICAD_MANAGER_ACTIONS::newProject.MakeEvent() ); - Go( &KICAD_MANAGER_CONTROL::NewFromTemplate, KICAD_MANAGER_ACTIONS::newFromTemplate.MakeEvent() ); Go( &KICAD_MANAGER_CONTROL::NewFromRepository, KICAD_MANAGER_ACTIONS::newFromRepository.MakeEvent() ); Go( &KICAD_MANAGER_CONTROL::NewJobsetFile, KICAD_MANAGER_ACTIONS::newJobsetFile.MakeEvent() ); Go( &KICAD_MANAGER_CONTROL::OpenDemoProject, KICAD_MANAGER_ACTIONS::openDemoProject.MakeEvent() ); diff --git a/kicad/tools/kicad_manager_control.h b/kicad/tools/kicad_manager_control.h index 0636f24250..303fe1b4c4 100644 --- a/kicad/tools/kicad_manager_control.h +++ b/kicad/tools/kicad_manager_control.h @@ -45,7 +45,6 @@ public: void Reset( RESET_REASON aReason ) override; int NewProject( const TOOL_EVENT& aEvent ); - int NewFromTemplate( const TOOL_EVENT& aEvent ); int NewFromRepository( const TOOL_EVENT& aEvent ); int NewJobsetFile( const TOOL_EVENT& aEvent ); int OpenProject( const TOOL_EVENT& aEvent );