Provide easy edit/duplicate template functions
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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 );
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 );
|
||||
|
||||
|
||||
Reference in New Issue
Block a user