Provide easy edit/duplicate template functions

This commit is contained in:
Seth Hillbrand
2025-10-14 17:19:08 -07:00
parent 741bcd35b5
commit 82029de1c2
6 changed files with 574 additions and 199 deletions
+1
View File
@@ -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
+237 -1
View File
@@ -34,6 +34,11 @@
#include <wx/bitmap.h>
#include <wx/image.h>
#include <wx/math.h>
#include <wx/menu.h>
#include <wx/textdlg.h>
#include <wx/textfile.h>
#include <confirm.h>
#include <project_tree_traverser.h>
#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 <title> and </title>
if( line.Contains( wxT( "<title>" ) ) && line.Contains( wxT( "</title>" ) ) )
{
int titleStart = line.Find( wxT( "<title>" ) );
int titleEnd = line.Find( wxT( "</title>" ) );
if( titleStart != wxNOT_FOUND && titleEnd != wxNOT_FOUND && titleEnd > titleStart )
{
wxString before = line.Left( titleStart + 7 ); // Include "<title>"
wxString after = line.Mid( titleEnd ); // Include "</title>" 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 );
+40 -2
View File
@@ -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<TEMPLATE_WIDGET*> m_allWidgets;
wxString m_projectToEdit; ///< Project path to edit instead of creating new
};
#endif
+214
View File
@@ -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 <kiway.h>
#include <kiway_express.h>
#include <project/project_file.h>
#include <project/project_local_settings.h>
#include <settings/settings_manager.h>
#include <gestfich.h>
#include <confirm.h>
#include <string_utils.h>
#include <richio.h>
#include <wildcards_and_files_ext.h>
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;
}
+67
View File
@@ -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 <wx/dir.h>
#include <wx/filename.h>
#include <wx/string.h>
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
+15 -196
View File
@@ -54,6 +54,7 @@
#include <project/project_archiver.h>
#include <project_tree_pane.h>
#include <project_tree.h>
#include <project_tree_traverser.h>
#include <launch_ext.h>
#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 );