From 82029de1c2cd62979242876dc68a73db594aaadc Mon Sep 17 00:00:00 2001 From: Seth Hillbrand Date: Tue, 14 Oct 2025 17:19:08 -0700 Subject: [PATCH] Provide easy edit/duplicate template functions --- kicad/CMakeLists.txt | 1 + kicad/dialogs/dialog_template_selector.cpp | 238 ++++++++++++++++++++- kicad/dialogs/dialog_template_selector.h | 42 +++- kicad/project_tree_traverser.cpp | 214 ++++++++++++++++++ kicad/project_tree_traverser.h | 67 ++++++ kicad/tools/kicad_manager_control.cpp | 211 ++---------------- 6 files changed, 574 insertions(+), 199 deletions(-) create mode 100644 kicad/project_tree_traverser.cpp create mode 100644 kicad/project_tree_traverser.h diff --git a/kicad/CMakeLists.txt b/kicad/CMakeLists.txt index 26b227cbad..502e488075 100644 --- a/kicad/CMakeLists.txt +++ b/kicad/CMakeLists.txt @@ -44,6 +44,7 @@ set( KICAD_SRCS project_tree_pane.cpp project_tree.cpp project_tree_item.cpp + project_tree_traverser.cpp update_manager.cpp toolbars_kicad_manager.cpp tools/kicad_manager_actions.cpp diff --git a/kicad/dialogs/dialog_template_selector.cpp b/kicad/dialogs/dialog_template_selector.cpp index 4de6010d08..036d43e8df 100644 --- a/kicad/dialogs/dialog_template_selector.cpp +++ b/kicad/dialogs/dialog_template_selector.cpp @@ -34,6 +34,11 @@ #include #include #include +#include +#include +#include +#include +#include #include "template_default_html.h" // Welcome / fallback HTML now provided by template_default_html.h @@ -44,6 +49,7 @@ TEMPLATE_SELECTION_PANEL::TEMPLATE_SELECTION_PANEL( wxNotebookPage* aParent, { m_parent = aParent; m_templatesPath = aPath; + m_isUserTemplates = false; } @@ -86,7 +92,7 @@ void TEMPLATE_SELECTION_PANEL::SortAlphabetically() { const wxString* a = aWidgetA->GetTemplate()->GetTitle(); const wxString* b = aWidgetB->GetTemplate()->GetTitle(); - + return ( *a ).CmpNoCase( *b ) < 0; }); @@ -109,6 +115,7 @@ TEMPLATE_WIDGET::TEMPLATE_WIDGET( wxWindow* aParent, DIALOG_TEMPLATE_SELECTOR* a { m_parent = aParent; m_dialog = aDialog; + m_isUserTemplate = false; // wxWidgets_3.xx way of doing the same... // Bind(wxEVT_LEFT_DOWN, &TEMPLATE_WIDGET::OnMouse, this ); @@ -118,6 +125,14 @@ TEMPLATE_WIDGET::TEMPLATE_WIDGET( wxWindow* aParent, DIALOG_TEMPLATE_SELECTOR* a m_staticTitle->Connect( wxEVT_LEFT_DOWN, wxMouseEventHandler( TEMPLATE_WIDGET::OnMouse ), nullptr, this ); + // Add right-click handler + m_bitmapIcon->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( TEMPLATE_WIDGET::onRightClick ), + nullptr, this ); + m_staticTitle->Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( TEMPLATE_WIDGET::onRightClick ), + nullptr, this ); + Connect( wxEVT_RIGHT_DOWN, wxMouseEventHandler( TEMPLATE_WIDGET::onRightClick ), + nullptr, this ); + // We're not selected until we're clicked Unselect(); @@ -183,6 +198,201 @@ void TEMPLATE_WIDGET::OnMouse( wxMouseEvent& event ) } +void TEMPLATE_WIDGET::onRightClick( wxMouseEvent& event ) +{ + // Only show context menu for user templates + if( !m_isUserTemplate || !m_currTemplate ) + { + event.Skip(); + return; + } + + wxMenu menu; + menu.Append( wxID_EDIT, _( "Edit Template" ) ); + menu.Append( wxID_COPY, _( "Duplicate Template" ) ); + + menu.Bind( wxEVT_COMMAND_MENU_SELECTED, + [this]( wxCommandEvent& evt ) + { + if( evt.GetId() == wxID_EDIT ) + onEditTemplate( evt ); + else if( evt.GetId() == wxID_COPY ) + onDuplicateTemplate( evt ); + } ); + + PopupMenu( &menu ); +} + + +void TEMPLATE_WIDGET::onEditTemplate( wxCommandEvent& event ) +{ + if( !m_currTemplate ) + return; + + // Get the template's base path + wxFileName templatePath = m_currTemplate->GetHtmlFile(); + templatePath.RemoveLastDir(); // Remove "meta" dir + + // Find a .kicad_pro file in the template directory + wxDir dir( templatePath.GetPath() ); + + if( !dir.IsOpened() ) + { + DisplayErrorMessage( m_dialog, + _( "Could not open template directory." ) ); + return; + } + + wxString filename; + bool found = dir.GetFirst( &filename, "*.kicad_pro", wxDIR_FILES ); + + if( !found ) + { + DisplayErrorMessage( m_dialog, + _( "No project file found in template directory." ) ); + return; + } + + wxFileName projectFile( templatePath.GetPath(), filename ); + + // Store the project path in the dialog so the caller can handle it + m_dialog->SetProjectToEdit( projectFile.GetFullPath() ); + + // Close with wxID_APPLY to indicate we want to edit, not create + m_dialog->EndModal( wxID_APPLY ); +} + + + +void TEMPLATE_WIDGET::onDuplicateTemplate( wxCommandEvent& event ) +{ + if( !m_currTemplate ) + return; + + // Get the template's base path + wxFileName templatePath = m_currTemplate->GetHtmlFile(); + templatePath.RemoveLastDir(); // Remove "meta" dir + wxString srcTemplatePath = templatePath.GetPath(); + wxString srcTemplateName = m_currTemplate->GetPrjDirName(); + + // Ask for new template name + wxTextEntryDialog nameDlg( m_dialog, + _( "Enter name for the new template:" ), + _( "Duplicate Template" ), + srcTemplateName + _( "_copy" ) ); + + if( nameDlg.ShowModal() != wxID_OK ) + return; + + wxString newTemplateName = nameDlg.GetValue(); + + if( newTemplateName.IsEmpty() ) + { + DisplayErrorMessage( m_dialog, _( "Template name cannot be empty." ) ); + return; + } + + // Get the user templates directory from the dialog + wxString userTemplatesPath = m_dialog->GetUserTemplatesPath(); + + if( userTemplatesPath.IsEmpty() ) + { + DisplayErrorMessage( m_dialog, _( "Could not find user templates directory." ) ); + return; + } + + // Create destination directory in user templates folder + wxFileName destPath( userTemplatesPath, wxEmptyString ); + destPath.AppendDir( newTemplateName ); + wxString newTemplatePath = destPath.GetPath(); + + if( destPath.DirExists() ) + { + DisplayErrorMessage( m_dialog, + wxString::Format( _( "Directory '%s' already exists." ), + newTemplatePath ) ); + return; + } + + if( !destPath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) ) + { + DisplayErrorMessage( m_dialog, + wxString::Format( _( "Could not create directory '%s'." ), + newTemplatePath ) ); + return; + } + + // Use shared traverser to copy all files with proper renaming + // Pass nullptr for frame to enable simple copy mode (no KIFACE handling) + wxDir sourceDir( srcTemplatePath ); + + if( !sourceDir.IsOpened() ) + { + DisplayErrorMessage( m_dialog, _( "Could not open source template directory." ) ); + return; + } + + PROJECT_TREE_TRAVERSER traverser( nullptr, srcTemplatePath, srcTemplateName, + newTemplatePath, newTemplateName ); + + sourceDir.Traverse( traverser ); + + if( !traverser.GetErrors().empty() ) + { + DisplayErrorMessage( m_dialog, traverser.GetErrors() ); + return; + } + + // Update the title in meta/info.html if it exists + wxFileName metaHtmlFile( newTemplatePath, "info.html" ); + metaHtmlFile.AppendDir( "meta" ); + + if( metaHtmlFile.FileExists() ) + { + wxTextFile htmlFile( metaHtmlFile.GetFullPath() ); + + if( htmlFile.Open() ) + { + bool modified = false; + + for( size_t i = 0; i < htmlFile.GetLineCount(); i++ ) + { + wxString line = htmlFile.GetLine( i ); + + // Update the title tag - replace content between and + if( line.Contains( wxT( "" ) ) && line.Contains( wxT( "" ) ) ) + { + int titleStart = line.Find( wxT( "" ) ); + int titleEnd = line.Find( wxT( "" ) ); + + if( titleStart != wxNOT_FOUND && titleEnd != wxNOT_FOUND && titleEnd > titleStart ) + { + wxString before = line.Left( titleStart + 7 ); // Include "" + wxString after = line.Mid( titleEnd ); // Include "" onwards + line = before + newTemplateName + after; + htmlFile[i] = line; + modified = true; + } + } + } + + if( modified ) + htmlFile.Write(); + + htmlFile.Close(); + } + } + + DisplayInfoMessage( m_dialog, + wxString::Format( _( "Template duplicated successfully to '%s'." ), + newTemplatePath ) ); + + // Refresh the widget list to show the new template + m_dialog->replaceCurrentPage(); +} + + + void DIALOG_TEMPLATE_SELECTOR::OnPageChange( wxNotebookEvent& event ) { int newPage = event.GetSelection(); @@ -236,6 +446,11 @@ DIALOG_TEMPLATE_SELECTOR::DIALOG_TEMPLATE_SELECTOR( wxWindow* aParent, const wxP wxString path = pathFname.GetFullPath(); // caller ensures this ends with file separator. TEMPLATE_SELECTION_PANEL* tpanel = new TEMPLATE_SELECTION_PANEL( m_notebook, path ); + + // Mark the first panel as "User Templates" if the title matches + if( title == _( "User Templates" ) ) + tpanel->SetIsUserTemplates( true ); + m_panels.push_back( tpanel ); m_notebook->AddPage( tpanel, title ); @@ -319,6 +534,7 @@ 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 ); + w->SetIsUserTemplate( m_panels[aPage]->IsUserTemplates() ); m_panels[aPage]->AddTemplateWidget( w ); m_allWidgets.push_back( w ); @@ -349,6 +565,20 @@ PROJECT_TEMPLATE* DIALOG_TEMPLATE_SELECTOR::GetDefaultTemplate() } +wxString DIALOG_TEMPLATE_SELECTOR::GetUserTemplatesPath() const +{ + // Find the first panel marked as user templates + for( const TEMPLATE_SELECTION_PANEL* panel : m_panels ) + { + if( panel->IsUserTemplates() ) + return panel->GetPath(); + } + + // If no user templates panel found, return empty string + return wxEmptyString; +} + + void DIALOG_TEMPLATE_SELECTOR::buildPageContent( const wxString& aPath, int aPage ) { // Get a list of files under the template path to include as choices... @@ -444,6 +674,11 @@ void DIALOG_TEMPLATE_SELECTOR::replaceCurrentPage() wxString title = m_notebook->GetPageText( page ); wxString currPath = m_tcTemplatePath->GetValue(); + // Save the user template flag before deleting + bool wasUserTemplates = false; + if( (unsigned)page < m_panels.size() ) + wasUserTemplates = m_panels[page]->IsUserTemplates(); + // Block all events to the notebook and its children wxEventBlocker blocker( m_notebook ); @@ -458,6 +693,7 @@ void DIALOG_TEMPLATE_SELECTOR::replaceCurrentPage() m_notebook->DeletePage( page ); TEMPLATE_SELECTION_PANEL* tpanel = new TEMPLATE_SELECTION_PANEL( m_notebook, currPath ); + tpanel->SetIsUserTemplates( wasUserTemplates ); // Restore the flag m_panels[page] = tpanel; m_notebook->InsertPage( page, tpanel, title, true ); diff --git a/kicad/dialogs/dialog_template_selector.h b/kicad/dialogs/dialog_template_selector.h index 6229fcfbee..2f1f5e4cdd 100644 --- a/kicad/dialogs/dialog_template_selector.h +++ b/kicad/dialogs/dialog_template_selector.h @@ -51,9 +51,19 @@ public: void Select(); void Unselect(); + /** + * Set whether this template widget represents a user template + * @param aIsUser true if this is a user template (can be edited/duplicated) + */ + void SetIsUserTemplate( bool aIsUser ) { m_isUserTemplate = aIsUser; } + bool IsUserTemplate() const { return m_isUserTemplate; } + protected: void OnKillFocus( wxFocusEvent& event ); void OnMouse( wxMouseEvent& event ); + void onRightClick( wxMouseEvent& event ); + void onEditTemplate( wxCommandEvent& event ); + void onDuplicateTemplate( wxCommandEvent& event ); private: bool IsSelected() { return m_selected; } @@ -63,6 +73,7 @@ protected: wxWindow* m_parent; wxPanel* m_panel; bool m_selected; + bool m_isUserTemplate; PROJECT_TEMPLATE* m_currTemplate; }; @@ -77,16 +88,23 @@ public: */ TEMPLATE_SELECTION_PANEL( wxNotebookPage* aParent, const wxString& aPath ); - const wxString& GetPath() { return m_templatesPath; } + const wxString& GetPath() const { return m_templatesPath; } void AddTemplateWidget( TEMPLATE_WIDGET* aTemplateWidget ); void SortAlphabetically(); + /** + * Set whether templates in this panel are user templates (can be edited/duplicated) + */ + void SetIsUserTemplates( bool aIsUser ) { m_isUserTemplates = aIsUser; } + bool IsUserTemplates() const { return m_isUserTemplates; } + protected: wxNotebookPage* m_parent; wxString m_templatesPath; ///< the path to access to the folder ///< containing the templates (which are also folders) + bool m_isUserTemplates; ///< true if this panel contains user templates }; @@ -105,6 +123,26 @@ public: void SetWidget( TEMPLATE_WIDGET* aWidget ); + /** + * @return the project path to edit (if Edit Template was selected), or empty string + */ + wxString GetProjectToEdit() const { return m_projectToEdit; } + + /** + * Set the project path to edit (used by template widgets) + */ + void SetProjectToEdit( const wxString& aPath ) { m_projectToEdit = aPath; } + + /** + * Refresh the current page to show updated template list + */ + void replaceCurrentPage(); + + /** + * Get the path to the user templates directory (first panel marked as user templates) + */ + wxString GetUserTemplatesPath() const; + protected: void AddTemplate( int aPage, PROJECT_TEMPLATE* aTemplate ); @@ -116,7 +154,6 @@ private: private: void buildPageContent( const wxString& aPath, int aPage ); - void replaceCurrentPage(); void OnPageChange( wxNotebookEvent& event ) override; void onDirectoryBrowseClicked( wxCommandEvent& event ) override; @@ -129,6 +166,7 @@ protected: TEMPLATE_WIDGET* m_defaultWidget; // Keep track of all template widgets so we can pick a sensible default std::vector m_allWidgets; + wxString m_projectToEdit; ///< Project path to edit instead of creating new }; #endif diff --git a/kicad/project_tree_traverser.cpp b/kicad/project_tree_traverser.cpp new file mode 100644 index 0000000000..0c79345aee --- /dev/null +++ b/kicad/project_tree_traverser.cpp @@ -0,0 +1,214 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#include "project_tree_traverser.h" +#include "kicad_manager_frame.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +PROJECT_TREE_TRAVERSER::PROJECT_TREE_TRAVERSER( KICAD_MANAGER_FRAME* aFrame, + const wxString& aSrcProjectDirPath, + const wxString& aSrcProjectName, + const wxString& aNewProjectDirPath, + const wxString& aNewProjectName ) : + m_frame( aFrame ), + m_projectDirPath( aSrcProjectDirPath ), + m_projectName( aSrcProjectName ), + m_newProjectDirPath( aNewProjectDirPath ), + m_newProjectName( aNewProjectName ) +{ +} + + +wxDirTraverseResult PROJECT_TREE_TRAVERSER::OnFile( const wxString& aSrcFilePath ) +{ + // Recursion guard for a Save As to a location inside the source project. + if( aSrcFilePath.StartsWith( m_newProjectDirPath + wxFileName::GetPathSeparator() ) ) + return wxDIR_CONTINUE; + + wxFileName destFile( aSrcFilePath ); + wxString ext = destFile.GetExt(); + bool atRoot = destFile.GetPath() == m_projectDirPath; + + if( ext == FILEEXT::LegacyProjectFileExtension + || ext == FILEEXT::ProjectFileExtension + || ext == FILEEXT::ProjectLocalSettingsFileExtension ) + { + wxString destPath = destFile.GetPath(); + + if( destPath.StartsWith( m_projectDirPath ) ) + { + destPath.Replace( m_projectDirPath, m_newProjectDirPath, false ); + destFile.SetPath( destPath ); + } + + if( destFile.GetName() == m_projectName ) + { + destFile.SetName( m_newProjectName ); + + if( atRoot && ext != FILEEXT::ProjectLocalSettingsFileExtension ) + m_newProjectFile = destFile; + } + + if( ext == FILEEXT::LegacyProjectFileExtension ) + { + // All paths in the settings file are relative so we can just do a straight copy + KiCopyFile( aSrcFilePath, destFile.GetFullPath(), m_errors ); + } + else if( ext == FILEEXT::ProjectFileExtension ) + { + PROJECT_FILE projectFile( aSrcFilePath ); + projectFile.LoadFromFile(); + projectFile.SaveAs( destFile.GetPath(), destFile.GetName() ); + } + else if( ext == FILEEXT::ProjectLocalSettingsFileExtension ) + { + PROJECT_LOCAL_SETTINGS projectLocalSettings( nullptr, aSrcFilePath ); + projectLocalSettings.LoadFromFile(); + projectLocalSettings.SaveAs( destFile.GetPath(), destFile.GetName() ); + } + } + else if( m_frame && ( ext == FILEEXT::KiCadSchematicFileExtension + || ext == FILEEXT::KiCadSchematicFileExtension + FILEEXT::BackupFileSuffix + || ext == FILEEXT::LegacySchematicFileExtension + || ext == FILEEXT::LegacySchematicFileExtension + FILEEXT::BackupFileSuffix + || ext == FILEEXT::SchematicSymbolFileExtension + || ext == FILEEXT::LegacySymbolLibFileExtension + || ext == FILEEXT::LegacySymbolDocumentFileExtension + || ext == FILEEXT::KiCadSymbolLibFileExtension + || ext == FILEEXT::NetlistFileExtension + || destFile.GetName() == FILEEXT::SymbolLibraryTableFileName ) ) + { + KIFACE* eeschema = m_frame->Kiway().KiFACE( KIWAY::FACE_SCH ); + eeschema->SaveFileAs( m_projectDirPath, m_projectName, m_newProjectDirPath, + m_newProjectName, aSrcFilePath, m_errors ); + } + else if( m_frame && ( ext == FILEEXT::KiCadPcbFileExtension + || ext == FILEEXT::KiCadPcbFileExtension + FILEEXT::BackupFileSuffix + || ext == FILEEXT::LegacyPcbFileExtension + || ext == FILEEXT::KiCadFootprintFileExtension + || ext == FILEEXT::LegacyFootprintLibPathExtension + || ext == FILEEXT::FootprintAssignmentFileExtension + || destFile.GetName() == FILEEXT::FootprintLibraryTableFileName ) ) + { + KIFACE* pcbnew = m_frame->Kiway().KiFACE( KIWAY::FACE_PCB ); + pcbnew->SaveFileAs( m_projectDirPath, m_projectName, m_newProjectDirPath, + m_newProjectName, aSrcFilePath, m_errors ); + } + else if( m_frame && ext == FILEEXT::DrawingSheetFileExtension ) + { + KIFACE* pleditor = m_frame->Kiway().KiFACE( KIWAY::FACE_PL_EDITOR ); + pleditor->SaveFileAs( m_projectDirPath, m_projectName, m_newProjectDirPath, + m_newProjectName, aSrcFilePath, m_errors ); + } + else if( m_frame && ( ext == FILEEXT::GerberJobFileExtension + || ext == FILEEXT::DrillFileExtension + || FILEEXT::IsGerberFileExtension( ext ) ) ) + { + KIFACE* gerbview = m_frame->Kiway().KiFACE( KIWAY::FACE_GERBVIEW ); + gerbview->SaveFileAs( m_projectDirPath, m_projectName, m_newProjectDirPath, + m_newProjectName, aSrcFilePath, m_errors ); + } + else if( destFile.GetName().StartsWith( FILEEXT::LockFilePrefix ) + && ext == FILEEXT::LockFileExtension ) + { + // Ignore lock files + } + else + { + // Everything we don't recognize just gets a straight copy. + wxString destPath = destFile.GetPathWithSep(); + wxString destName = destFile.GetName(); + wxUniChar pathSep = wxFileName::GetPathSeparator(); + + wxString srcProjectFootprintLib = pathSep + m_projectName + ".pretty" + pathSep; + wxString newProjectFootprintLib = pathSep + m_newProjectName + ".pretty" + pathSep; + + if( destPath.StartsWith( m_projectDirPath ) ) + destPath.Replace( m_projectDirPath, m_newProjectDirPath, false ); + + destPath.Replace( srcProjectFootprintLib, newProjectFootprintLib, true ); + + if( destName == m_projectName && ext != wxT( "zip" ) /* don't rename archives */ ) + destFile.SetName( m_newProjectName ); + + destFile.SetPath( destPath ); + + KiCopyFile( aSrcFilePath, destFile.GetFullPath(), m_errors ); + } + + return wxDIR_CONTINUE; +} + + +wxDirTraverseResult PROJECT_TREE_TRAVERSER::OnDir( const wxString& aSrcDirPath ) +{ + // Recursion guard for a Save As to a location inside the source project. + if( aSrcDirPath.StartsWith( m_newProjectDirPath ) ) + return wxDIR_CONTINUE; + + wxFileName destDir( aSrcDirPath ); + wxString destDirPath = destDir.GetPathWithSep(); + wxUniChar pathSep = wxFileName::GetPathSeparator(); + + if( destDirPath.StartsWith( m_projectDirPath + pathSep ) + || destDirPath.StartsWith( m_projectDirPath + PROJECT_BACKUPS_DIR_SUFFIX ) ) + { + destDirPath.Replace( m_projectDirPath, m_newProjectDirPath, false ); + destDir.SetPath( destDirPath ); + } + + if( destDir.GetName() == m_projectName ) + { + if( destDir.GetExt() == "pretty" ) + destDir.SetName( m_newProjectName ); +#if 0 + // WAYNE STAMBAUGH TODO: + // If we end up with a symbol equivalent to ".pretty" we'll want to handle it here.... + else if( destDir.GetExt() == "sym_lib_dir_extension" ) + destDir.SetName( m_newProjectName ); +#endif + } + + if( !wxMkdir( destDir.GetFullPath() ) ) + { + wxString msg; + + if( !m_errors.empty() ) + m_errors += "\n"; + + msg.Printf( _( "Cannot copy folder '%s'." ), destDir.GetFullPath() ); + m_errors += msg; + } + + return wxDIR_CONTINUE; +} diff --git a/kicad/project_tree_traverser.h b/kicad/project_tree_traverser.h new file mode 100644 index 0000000000..acaf1c5a45 --- /dev/null +++ b/kicad/project_tree_traverser.h @@ -0,0 +1,67 @@ +/* + * This program source code file is part of KiCad, a free EDA CAD application. + * + * Copyright The KiCad Developers, see AUTHORS.txt for contributors. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, you may find one here: + * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html + * or you may search the http://www.gnu.org website for the version 2 license, + * or you may write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA + */ + +#ifndef PROJECT_TREE_TRAVERSER_H +#define PROJECT_TREE_TRAVERSER_H + +#include +#include +#include + +class KICAD_MANAGER_FRAME; + +/** + * Traverser class to duplicate/copy project or template files with proper renaming. + * + * This class can operate in two modes: + * 1. With KICAD_MANAGER_FRAME: Uses KIFACE interfaces for proper file type handling + * 2. Without KICAD_MANAGER_FRAME (nullptr): Simple file copying for templates + */ +class PROJECT_TREE_TRAVERSER : public wxDirTraverser +{ +public: + PROJECT_TREE_TRAVERSER( KICAD_MANAGER_FRAME* aFrame, + const wxString& aSrcProjectDirPath, + const wxString& aSrcProjectName, + const wxString& aNewProjectDirPath, + const wxString& aNewProjectName ); + + virtual wxDirTraverseResult OnFile( const wxString& aSrcFilePath ) override; + virtual wxDirTraverseResult OnDir( const wxString& aSrcDirPath ) override; + + wxString GetErrors() const { return m_errors; } + wxFileName GetNewProjectFile() const { return m_newProjectFile; } + +private: + KICAD_MANAGER_FRAME* m_frame; // nullptr for simple copy mode (templates) + + wxString m_projectDirPath; + wxString m_projectName; + wxString m_newProjectDirPath; + wxString m_newProjectName; + + wxFileName m_newProjectFile; + wxString m_errors; +}; + +#endif // PROJECT_TREE_TRAVERSER_H diff --git a/kicad/tools/kicad_manager_control.cpp b/kicad/tools/kicad_manager_control.cpp index 94773a236a..8ccddbee4c 100644 --- a/kicad/tools/kicad_manager_control.cpp +++ b/kicad/tools/kicad_manager_control.cpp @@ -54,6 +54,7 @@ #include #include #include +#include #include #include "widgets/filedlg_new_project.h" @@ -231,6 +232,18 @@ int KICAD_MANAGER_CONTROL::NewProject( const TOOL_EVENT& aEvent ) settings->m_TemplateWindowPos = ps.GetPosition(); settings->m_TemplateWindowSize = ps.GetSize(); + // Check if user wants to edit a template instead of creating new project + if( result == wxID_APPLY ) + { + wxString projectToEdit = ps.GetProjectToEdit(); + + if( !projectToEdit.IsEmpty() && wxFileExists( projectToEdit ) ) + { + m_frame->LoadProject( wxFileName( projectToEdit ) ); + return 0; + } + } + if( result != wxID_OK ) return -1; @@ -559,200 +572,6 @@ int KICAD_MANAGER_CONTROL::ViewDroppedViewers( const TOOL_EVENT& aEvent ) return 0; } -class SAVE_AS_TRAVERSER : public wxDirTraverser -{ -public: - SAVE_AS_TRAVERSER( KICAD_MANAGER_FRAME* aFrame, - const wxString& aSrcProjectDirPath, - const wxString& aSrcProjectName, - const wxString& aNewProjectDirPath, - const wxString& aNewProjectName ) : - m_frame( aFrame ), - m_projectDirPath( aSrcProjectDirPath ), - m_projectName( aSrcProjectName ), - m_newProjectDirPath( aNewProjectDirPath ), - m_newProjectName( aNewProjectName ) - { - } - - virtual wxDirTraverseResult OnFile( const wxString& aSrcFilePath ) override - { - // Recursion guard for a Save As to a location inside the source project. - if( aSrcFilePath.StartsWith( m_newProjectDirPath + wxFileName::GetPathSeparator() ) ) - return wxDIR_CONTINUE; - - wxFileName destFile( aSrcFilePath ); - wxString ext = destFile.GetExt(); - bool atRoot = destFile.GetPath() == m_projectDirPath; - - if( ext == FILEEXT::LegacyProjectFileExtension - || ext == FILEEXT::ProjectFileExtension - || ext == FILEEXT::ProjectLocalSettingsFileExtension ) - { - wxString destPath = destFile.GetPath(); - - if( destPath.StartsWith( m_projectDirPath ) ) - { - destPath.Replace( m_projectDirPath, m_newProjectDirPath, false ); - destFile.SetPath( destPath ); - } - - if( destFile.GetName() == m_projectName ) - { - destFile.SetName( m_newProjectName ); - - if( atRoot && ext != FILEEXT::ProjectLocalSettingsFileExtension ) - m_newProjectFile = destFile; - } - - if( ext == FILEEXT::LegacyProjectFileExtension ) - { - // All paths in the settings file are relative so we can just do a straight copy - KiCopyFile( aSrcFilePath, destFile.GetFullPath(), m_errors ); - } - else if( ext == FILEEXT::ProjectFileExtension ) - { - PROJECT_FILE projectFile( aSrcFilePath ); - projectFile.LoadFromFile(); - projectFile.SaveAs( destFile.GetPath(), destFile.GetName() ); - } - else if( ext == FILEEXT::ProjectLocalSettingsFileExtension ) - { - PROJECT_LOCAL_SETTINGS projectLocalSettings( nullptr, aSrcFilePath ); - projectLocalSettings.LoadFromFile(); - projectLocalSettings.SaveAs( destFile.GetPath(), destFile.GetName() ); - } - } - else if( ext == FILEEXT::KiCadSchematicFileExtension - || ext == FILEEXT::KiCadSchematicFileExtension + FILEEXT::BackupFileSuffix - || ext == FILEEXT::LegacySchematicFileExtension - || ext == FILEEXT::LegacySchematicFileExtension + FILEEXT::BackupFileSuffix - || ext == FILEEXT::SchematicSymbolFileExtension - || ext == FILEEXT::LegacySymbolLibFileExtension - || ext == FILEEXT::LegacySymbolDocumentFileExtension - || ext == FILEEXT::KiCadSymbolLibFileExtension - || ext == FILEEXT::NetlistFileExtension - || destFile.GetName() == FILEEXT::SymbolLibraryTableFileName ) - { - KIFACE* eeschema = m_frame->Kiway().KiFACE( KIWAY::FACE_SCH ); - eeschema->SaveFileAs( m_projectDirPath, m_projectName, m_newProjectDirPath, - m_newProjectName, aSrcFilePath, m_errors ); - } - else if( ext == FILEEXT::KiCadPcbFileExtension - || ext == FILEEXT::KiCadPcbFileExtension + FILEEXT::BackupFileSuffix - || ext == FILEEXT::LegacyPcbFileExtension - || ext == FILEEXT::KiCadFootprintFileExtension - || ext == FILEEXT::LegacyFootprintLibPathExtension - || ext == FILEEXT::FootprintAssignmentFileExtension - || destFile.GetName() == FILEEXT::FootprintLibraryTableFileName ) - { - KIFACE* pcbnew = m_frame->Kiway().KiFACE( KIWAY::FACE_PCB ); - pcbnew->SaveFileAs( m_projectDirPath, m_projectName, m_newProjectDirPath, - m_newProjectName, aSrcFilePath, m_errors ); - } - else if( ext == FILEEXT::DrawingSheetFileExtension ) - { - KIFACE* pleditor = m_frame->Kiway().KiFACE( KIWAY::FACE_PL_EDITOR ); - pleditor->SaveFileAs( m_projectDirPath, m_projectName, m_newProjectDirPath, - m_newProjectName, aSrcFilePath, m_errors ); - } - else if( ext == FILEEXT::GerberJobFileExtension - || ext == FILEEXT::DrillFileExtension - || FILEEXT::IsGerberFileExtension( ext ) ) - { - KIFACE* gerbview = m_frame->Kiway().KiFACE( KIWAY::FACE_GERBVIEW ); - gerbview->SaveFileAs( m_projectDirPath, m_projectName, m_newProjectDirPath, - m_newProjectName, aSrcFilePath, m_errors ); - } - else if( destFile.GetName().StartsWith( FILEEXT::LockFilePrefix ) - && ext == FILEEXT::LockFileExtension ) - { - // Ignore lock files - } - else - { - // Everything we don't recognize just gets a straight copy. - wxString destPath = destFile.GetPathWithSep(); - wxString destName = destFile.GetName(); - wxUniChar pathSep = wxFileName::GetPathSeparator(); - - wxString srcProjectFootprintLib = pathSep + m_projectName + ".pretty" + pathSep; - wxString newProjectFootprintLib = pathSep + m_newProjectName + ".pretty" + pathSep; - - if( destPath.StartsWith( m_projectDirPath ) ) - destPath.Replace( m_projectDirPath, m_newProjectDirPath, false ); - - destPath.Replace( srcProjectFootprintLib, newProjectFootprintLib, true ); - - if( destName == m_projectName && ext != wxT( "zip" ) /* don't rename archives */ ) - destFile.SetName( m_newProjectName ); - - destFile.SetPath( destPath ); - - KiCopyFile( aSrcFilePath, destFile.GetFullPath(), m_errors ); - } - - return wxDIR_CONTINUE; - } - - virtual wxDirTraverseResult OnDir( const wxString& aSrcDirPath ) override - { - // Recursion guard for a Save As to a location inside the source project. - if( aSrcDirPath.StartsWith( m_newProjectDirPath ) ) - return wxDIR_CONTINUE; - - wxFileName destDir( aSrcDirPath ); - wxString destDirPath = destDir.GetPathWithSep(); - wxUniChar pathSep = wxFileName::GetPathSeparator(); - - if( destDirPath.StartsWith( m_projectDirPath + pathSep ) - || destDirPath.StartsWith( m_projectDirPath + PROJECT_BACKUPS_DIR_SUFFIX ) ) - { - destDirPath.Replace( m_projectDirPath, m_newProjectDirPath, false ); - destDir.SetPath( destDirPath ); - } - - if( destDir.GetName() == m_projectName ) - { - if( destDir.GetExt() == "pretty" ) - destDir.SetName( m_newProjectName ); -#if 0 - // WAYNE STAMBAUGH TODO: - // If we end up with a symbol equivalent to ".pretty" we'll want to handle it here.... - else if( destDir.GetExt() == "sym_lib_dir_extension" ) - destDir.SetName( m_newProjectName ); -#endif - } - - if( !wxMkdir( destDir.GetFullPath() ) ) - { - wxString msg; - - if( !m_errors.empty() ) - m_errors += "\n"; - - msg.Printf( _( "Cannot copy folder '%s'." ), destDir.GetFullPath() ); - m_errors += msg; - } - - return wxDIR_CONTINUE; - } - - wxString GetErrors() { return m_errors; } - - wxFileName GetNewProjectFile() { return m_newProjectFile; } - -private: - KICAD_MANAGER_FRAME* m_frame; - - wxString m_projectDirPath; - wxString m_projectName; - wxString m_newProjectDirPath; - wxString m_newProjectName; - - wxFileName m_newProjectFile; - wxString m_errors; -}; int KICAD_MANAGER_CONTROL::SaveProjectAs( const TOOL_EVENT& aEvent ) @@ -815,8 +634,8 @@ int KICAD_MANAGER_CONTROL::SaveProjectAs( const TOOL_EVENT& aEvent ) const wxString& newProjectName = newProjectDir.GetDirs().Last(); wxDir currentProjectDir( currentProjectDirPath ); - SAVE_AS_TRAVERSER traverser( m_frame, currentProjectDirPath, currentProjectName, newProjectDirPath, - newProjectName ); + PROJECT_TREE_TRAVERSER traverser( m_frame, currentProjectDirPath, currentProjectName, + newProjectDirPath, newProjectName ); currentProjectDir.Traverse( traverser );