Installment one of project Save As... feature.

ADD: Adds Save As... to the File menu for the project window.

Fixes: lp:594051
* https://bugs.launchpad.net/kicad/+bug/594051
This commit is contained in:
Jeff Young 2019-11-09 19:39:08 +00:00
parent d6e0ec2f24
commit b5904b0401
15 changed files with 788 additions and 38 deletions

View File

@ -359,6 +359,21 @@ bool CanPrintFile( const wxString& file )
}
void CopyFile( const wxString& aSrcPath, const wxString& aDestPath, std::string& aErrors )
{
if( !wxCopyFile( aSrcPath, aDestPath ) )
{
wxString msg;
if( !aErrors.empty() )
aErrors += "\n";
msg.Printf( _( "Cannot copy file \"%s\"." ), aDestPath );
aErrors += msg;
}
}
wxString QuoteFullPath( wxFileName& fn, wxPathFormat format )
{
return wxT( "\"" ) + fn.GetFullPath( format ) + wxT( "\"" );

View File

@ -28,6 +28,7 @@ include_directories(
./tools
../common
../common/dialogs
../libs/sexpr/include
${INC_AFTER}
)
@ -353,6 +354,7 @@ target_include_directories( eeschema_kiface PUBLIC
target_link_libraries( eeschema_kiface
common
sexpr
${wxWidgets_LIBRARIES}
${GDI_PLUS_LIBRARIES}
)

View File

@ -32,7 +32,6 @@
#include <sch_edit_frame.h>
#include <lib_edit_frame.h>
#include <viewlib_frame.h>
#include <eda_text.h>
#include <general.h>
#include <class_libentry.h>
#include <transform.h>
@ -42,6 +41,9 @@
#include <dialogs/panel_sym_lib_table.h>
#include <kiway.h>
#include <sim/sim_plot_frame.h>
#include <kiface_ids.h>
#include <libs/sexpr/include/sexpr/sexpr.h>
#include <libs/sexpr/include/sexpr/sexpr_parser.h>
// The main sheet of the project
SCH_SHEET* g_RootSheet = NULL;
@ -127,6 +129,16 @@ static struct IFACE : public KIFACE_I
return NULL;
}
/**
* Function SaveFileAs
* Saving a file under a different name is delegated to the various KIFACEs because
* the project doesn't know the internal format of the various files (which may have
* paths in them that need updating).
*/
void SaveFileAs( const std::string& aProjectBasePath, const std::string& aProjectName,
const std::string& aNewProjectBasePath, const std::string& aNewProjectName,
const std::string& aSrcFilePath, std::string& aErrors ) override;
} kiface( "eeschema", KIWAY::FACE_SCH );
} // namespace
@ -291,3 +303,149 @@ void IFACE::OnKifaceEnd()
wxConfigSaveSetups( KifaceSettings(), cfg_params() );
end_common();
}
static void traverseSEXPR( SEXPR::SEXPR* aNode,
const std::function<void( SEXPR::SEXPR* )>& aVisitor )
{
aVisitor( aNode );
if( aNode->IsList() )
{
for( int i = 0; i < aNode->GetNumberOfChildren(); i++ )
traverseSEXPR( aNode->GetChild( i ), aVisitor );
}
}
void IFACE::SaveFileAs( const std::string& aProjectBasePath, const std::string& aProjectName,
const std::string& aNewProjectBasePath, const std::string& aNewProjectName,
const std::string& aSrcFilePath, std::string& aErrors )
{
wxFileName destFile( aSrcFilePath );
wxString destPath = destFile.GetPath();
wxString ext = destFile.GetExt();
if( destPath.StartsWith( aProjectBasePath ) )
{
destPath.Replace( aProjectBasePath, aNewProjectBasePath, false );
destFile.SetPath( destPath );
}
if( ext == "sch" || ext == "sch-bak" )
{
if( destFile.GetName() == aProjectName )
destFile.SetName( aNewProjectName );
// JEY TODO: need to update at least sheet-paths...
CopyFile( aSrcFilePath, destFile.GetFullPath(), aErrors );
}
else if( ext == "sym" )
{
// Symbols are not project-specific. Keep their source names.
wxCopyFile( aSrcFilePath, destFile.GetFullPath() );
}
else if( ext == "lib" )
{
if( destFile.GetName() == aProjectName )
destFile.SetName( aNewProjectName );
else if( destFile.GetName() == aProjectName + "-cache" )
destFile.SetName( aNewProjectName + "-cache" );
else if( destFile.GetName() == aProjectName + "-rescue" )
destFile.SetName( aNewProjectName + "-rescue" );
CopyFile( aSrcFilePath, destFile.GetFullPath(), aErrors );
}
else if( ext == "net" )
{
bool success = false;
if( destFile.GetName() == aProjectName )
destFile.SetName( aNewProjectName );
try
{
SEXPR::PARSER parser;
std::unique_ptr<SEXPR::SEXPR> sexpr( parser.ParseFromFile( aSrcFilePath ) );
traverseSEXPR( sexpr.get(), [&]( SEXPR::SEXPR* node )
{
if( node->IsList() && node->GetNumberOfChildren() > 1
&& node->GetChild( 0 )->IsSymbol()
&& node->GetChild( 0 )->GetSymbol() == "source" )
{
auto pathNode = dynamic_cast<SEXPR::SEXPR_STRING*>( node->GetChild( 1 ) );
wxString path( pathNode->m_value );
if( path == aProjectName + ".sch" )
path = aNewProjectName + ".sch";
else if( path == aProjectBasePath + "/" + aProjectName + ".sch" )
path = aNewProjectBasePath + "/" + aNewProjectName + ".sch";
else if( path.StartsWith( aProjectBasePath ) )
path.Replace( aProjectBasePath, aNewProjectBasePath, false );
pathNode->m_value = path;
}
} );
wxFile destNetList( destFile.GetFullPath(), wxFile::write );
if( destNetList.IsOpened() )
success = destNetList.Write( sexpr->AsString( 0 ) );
// wxFile dtor will close the file
}
catch( ... )
{
success = false;
}
if( !success )
{
wxString msg;
if( !aErrors.empty() )
aErrors += "\n";
msg.Printf( _( "Cannot copy file \"%s\"." ), destFile.GetFullPath() );
aErrors += msg;
}
}
else if( destFile.GetName() == "sym-lib-table" )
{
SYMBOL_LIB_TABLE symbolLibTable;
symbolLibTable.Load( aSrcFilePath );
for( int i = 0; i < symbolLibTable.GetCount(); i++ )
{
LIB_TABLE_ROW& row = symbolLibTable.At( i );
wxString uri = row.GetFullURI();
uri.Replace( "/" + aProjectName + "-cache.lib", "/" + aNewProjectName + "-cache.lib" );
uri.Replace( "/" + aProjectName + "-rescue.lib", "/" + aNewProjectName + "-rescue.lib" );
uri.Replace( "/" + aProjectName + ".lib", "/" + aNewProjectName + ".lib" );
row.SetFullURI( uri );
}
try
{
symbolLibTable.Save( destFile.GetFullPath() );
}
catch( ... )
{
wxString msg;
if( !aErrors.empty() )
aErrors += "\n";
msg.Printf( _( "Cannot copy file \"%s\"." ), destFile.GetFullPath() );
aErrors += msg;
}
}
else
{
wxFAIL_MSG( "Unexpected filetype for Eeschema::SaveFileAs()" );
}
}

View File

@ -31,6 +31,8 @@
#include <pgm_base.h>
#include <gerbview.h>
#include <gerbview_frame.h>
#include <gestfich.h>
#include "json11.hpp"
const wxChar* g_GerberPageSizeList[] =
{
@ -92,6 +94,16 @@ static struct IFACE : public KIFACE_I
return NULL;
}
/**
* Function SaveFileAs
* Saving a file under a different name is delegated to the various KIFACEs because
* the project doesn't know the internal format of the various files (which may have
* paths in them that need updating).
*/
void SaveFileAs( const std::string& aProjectBasePath, const std::string& aProjectName,
const std::string& aNewProjectBasePath, const std::string& aNewProjectName,
const std::string& aSrcFilePath, std::string& aErrors ) override;
} kiface( "gerbview", KIWAY::FACE_GERBVIEW );
} // namespace
@ -130,3 +142,114 @@ void IFACE::OnKifaceEnd()
{
end_common();
}
void IFACE::SaveFileAs( const std::string& aProjectBasePath, const std::string& aProjectName,
const std::string& aNewProjectBasePath, const std::string& aNewProjectName,
const std::string& aSrcFilePath, std::string& aErrors )
{
wxFileName destFile( aSrcFilePath );
wxString destPath = destFile.GetPath();
wxString ext = destFile.GetExt();
if( destPath.StartsWith( aProjectBasePath ) )
{
destPath.Replace( aProjectBasePath, aNewProjectBasePath, false );
destFile.SetPath( destPath );
}
if( ext == "gbr" )
{
wxString destFileName = destFile.GetName();
if( destFileName.StartsWith( aProjectName + "-" ) )
{
destFileName.Replace( aProjectName, aNewProjectName, false );
destFile.SetName( destFileName );
}
wxCopyFile( aSrcFilePath, destFile.GetFullPath() );
}
else if( ext == "gbrjob" )
{
if( destFile.GetName() == aProjectName + "-job" )
destFile.SetName( aNewProjectName + "-job" );
FILE_LINE_READER jobfileReader( aSrcFilePath );
char* line;
wxString data;
while( ( line = jobfileReader.ReadLine() ) )
data << line << '\n';
// detect the file format: old (deprecated) gerber format or official JSON format
if( !data.Contains( "{" ) )
{
CopyFile( aSrcFilePath, destFile.GetFullPath(), aErrors );
return;
}
bool success = false;
try
{
std::string err;
json11::Json json = json11::Json::parse(TO_UTF8( data ), err );
if( err.empty() )
{
for( auto& entry : json[ "FilesAttributes" ].array_items() )
{
wxString path = entry[ "Path" ].string_value();
if( path.StartsWith( aProjectName + "-" ) )
{
path.Replace( aProjectName, aNewProjectName, false );
entry[ "Path" ].set_string_value( path.ToStdString() );
}
}
wxFile destJobFile( destFile.GetFullPath(), wxFile::write );
if( destJobFile.IsOpened() )
success = destJobFile.Write( json.dump( 0 ) );
// wxFile dtor will close the file
}
}
catch( ... )
{
success = false;
}
if( !success )
{
wxString msg;
if( !aErrors.empty() )
aErrors += "\n";
msg.Printf( _( "Cannot copy file \"%s\"." ), destFile.GetFullPath() );
aErrors += msg;
}
}
else if( ext == "drl" )
{
wxString destFileName = destFile.GetName();
if( destFileName == aProjectName )
destFileName = aNewProjectName;
else if( destFileName.StartsWith( aProjectName + "-" ) )
destFileName.Replace( aProjectName, aNewProjectName, false );
destFile.SetName( destFileName );
CopyFile( aSrcFilePath, destFile.GetFullPath(), aErrors );
}
else
{
wxFAIL_MSG( "Unexpected filetype for GerbView::SaveFileAs()" );
}
}

View File

@ -50,18 +50,27 @@ struct NullStruct
* Serialization
*/
static void dump( NullStruct, string& out )
static void indent( int level, string& out )
{
for( int i = 0; i < level * 2; i++ )
out += " ";
}
static void dump( NullStruct, int level, string& out )
{
out += "null";
}
static void dump( double value, string& out )
static void dump( double value, int level, string& out )
{
indent( level, out );
if( std::isfinite( value ) )
{
char buf[32];
snprintf( buf, sizeof buf, "%.17g", value );
snprintf( buf, sizeof buf, "%.3f", value );
out += buf;
}
else
@ -71,8 +80,10 @@ static void dump( double value, string& out )
}
static void dump( int value, string& out )
static void dump( int value, int level, string& out )
{
indent( level, out );
char buf[32];
snprintf( buf, sizeof buf, "%d", value );
@ -80,14 +91,18 @@ static void dump( int value, string& out )
}
static void dump( bool value, string& out )
static void dump( bool value, int level, string& out )
{
indent( level, out );
out += value ? "true" : "false";
}
static void dump( const string& value, string& out )
static void dump( const string& value, int level, string& out )
{
indent( level, out );
out += '"';
for( size_t i = 0; i < value.length(); i++ )
@ -150,49 +165,71 @@ static void dump( const string& value, string& out )
}
static void dump( const Json::array& values, string& out )
static void dump( const Json::array& values, int level, string& out )
{
bool first = true;
out += "[";
indent( level, out );
out += "[\n";
for( const auto& value : values )
{
if( !first )
out += ", ";
out += ",\n";
value.dump( out );
value.dump( level + 1, out );
first = false;
}
out += "\n";
indent( level, out );
out += "]";
}
static void dump( const Json::object& values, string& out )
static void dump( const Json::object& values, int level, string& out )
{
bool first = true;
out += "{";
indent( level, out );
out += "{\n";
for( int i : { 1, 2 } )
{
for( const auto& kv : values )
{
if( !first )
out += ", ";
if( i == 1 && kv.first != "Header" )
continue;
else if( i == 2 && kv.first == "Header" )
continue;
dump( kv.first, out );
if( !first )
out += ",\n";
dump( kv.first, level + 1, out );
out += ": ";
kv.second.dump( out );
if( kv.second.is_object() || kv.second.is_array() )
{
out += "\n";
kv.second.dump( level + 1, out );
}
else
{
kv.second.dump( 1, out );
}
first = false;
}
}
out += "\n";
indent( level, out );
out += "}";
}
void Json::dump( string& out ) const
void Json::dump( int level, string& out ) const
{
m_ptr->dump( out );
m_ptr->dump( level, out );
}
@ -226,8 +263,8 @@ protected:
return m_value < static_cast<const Value<tag, T>*>(other)->m_value;
}
const T m_value;
void dump( string& out ) const override { json11::dump( m_value, out ); }
T m_value;
void dump( int level, string& out ) const override { json11::dump( m_value, level, out ); }
};
class JsonDouble final : public Value<Json::NUMBER, double>
@ -276,6 +313,11 @@ class JsonString final : public Value<Json::STRING, string>
{
const string& string_value() const override { return m_value; }
void set_string_value( string value ) const override
{
const_cast<JsonString*>( this )->m_value = value;
}
public:
explicit JsonString( const string& value ) : Value( value ) {}
explicit JsonString( string&& value ) : Value( move( value ) ) {}
@ -436,6 +478,12 @@ const string& Json::string_value() const
}
void Json::set_string_value( string value ) const
{
m_ptr->set_string_value( value );
}
const vector<Json>& Json::array_items() const
{
return m_ptr->array_items();
@ -483,6 +531,10 @@ const string& JsonValue::string_value() const
return statics().empty_string;
}
void JsonValue::set_string_value( string value ) const
{
}
const vector<Json>& JsonValue::array_items() const
{
@ -1135,7 +1187,7 @@ bool Json::has_shape( const shape& types, string& err ) const
{
if( !is_object() )
{
err = "expected JSON object, got " + dump();
err = "expected JSON object, got " + dump( 0 );
return false;
}
@ -1143,7 +1195,7 @@ bool Json::has_shape( const shape& types, string& err ) const
{
if( (*this)[item.first].type() != item.second )
{
err = "bad type for " + item.first + " in " + dump();
err = "bad type for " + item.first + " in " + dump( 0 );
return false;
}
}

View File

@ -148,6 +148,8 @@ public:
// Return the enclosed string if this is a string, "" otherwise.
const std::string& string_value() const;
void set_string_value( std::string value ) const;
// Return the enclosed std::vector if this is an array, or an empty vector otherwise.
const array& array_items() const;
@ -161,13 +163,13 @@ public:
const Json& operator[]( const std::string& key ) const;
// Serialize.
void dump( std::string& out ) const;
void dump( int level, std::string& out ) const;
std::string dump() const
std::string dump( int level ) const
{
std::string out;
dump( out );
dump( level, out );
return out;
}
@ -236,11 +238,12 @@ protected:
virtual Json::Type type() const = 0;
virtual bool equals( const JsonValue* other ) const = 0;
virtual bool less( const JsonValue* other ) const = 0;
virtual void dump( std::string& out ) const = 0;
virtual void dump( int level, std::string& out ) const = 0;
virtual double number_value() const;
virtual int int_value() const;
virtual bool bool_value() const;
virtual const std::string& string_value() const;
virtual void set_string_value( std::string value ) const;
virtual const Json::array& array_items() const;
virtual const Json& operator[]( size_t i ) const;
virtual const Json::object& object_items() const;

View File

@ -59,6 +59,14 @@ void OpenFile( const wxString& file );
void PrintFile( const wxString& file );
bool CanPrintFile( const wxString& file );
/**
* Function CopyFile
* @param aSrcPath
* @param aDestPath
* @param aErrors a string to *append* any errors to
*/
void CopyFile( const wxString& aSrcPath, const wxString& aDestPath, std::string& aErrors );
/**
* Function EDA_FILE_SELECTOR
*

View File

@ -211,6 +211,22 @@ struct KIFACE
VTBL_ENTRY wxWindow* CreateWindow( wxWindow* aParent, int aClassId,
KIWAY* aKIWAY, int aCtlBits = 0 ) = 0;
/**
* Function SaveFileAs
* Saving a file under a different name is delegated to the various KIFACEs because
* the project doesn't know the internal format of the various files (which may have
* paths in them that need updating).
*/
VTBL_ENTRY void SaveFileAs( const std::string& srcProjectBasePath,
const std::string& srcProjectName,
const std::string& newProjectBasePath,
const std::string& newProjectName,
const std::string& srcFilePath,
std::string& aErrors )
{
// If a KIFACE owns files then it needs to implement this....
}
/**
* Function IfaceOrAddress
* returns a pointer to the requested object. The safest way to use this

View File

@ -66,6 +66,9 @@ void KICAD_MANAGER_FRAME::ReCreateMenuBar()
fileMenu->AddItem( KICAD_MANAGER_ACTIONS::openProject, SELECTION_CONDITIONS::ShowAlways );
fileMenu->AddMenu( openRecentMenu, SELECTION_CONDITIONS::ShowAlways );
fileMenu->AddSeparator();
fileMenu->AddItem( ACTIONS::saveAs, SELECTION_CONDITIONS::ShowAlways );
fileMenu->AddSeparator();
fileMenu->AddItem( ID_IMPORT_EAGLE_PROJECT,
_( "Import EAGLE Project..." ),

View File

@ -51,7 +51,7 @@ TOOL_ACTION KICAD_MANAGER_ACTIONS::openProject( "kicad.Control.openProject",
AS_GLOBAL,
MD_CTRL + 'O', LEGACY_HK_NAME( "Open Project" ),
_( "Open Project..." ), _( "Open an existing project" ),
open_project_xpm );
directory_xpm );
TOOL_ACTION KICAD_MANAGER_ACTIONS::editSchematic( "kicad.Control.editSchematic",
AS_GLOBAL,

View File

@ -313,6 +313,242 @@ int KICAD_MANAGER_CONTROL::OpenProject( const TOOL_EVENT& aEvent )
}
class SAVE_AS_TRAVERSER : public wxDirTraverser
{
private:
KICAD_MANAGER_FRAME* m_frame;
wxString m_projectDirPath;
wxString m_projectName;
wxString m_newProjectDirPath;
wxString m_newProjectName;
wxFileName m_newProjectFile;
std::string m_errors;
public:
SAVE_AS_TRAVERSER( KICAD_MANAGER_FRAME* aFrame,
const std::string& aSrcProjectDirPath,
const std::string& aSrcProjectName,
const std::string& aNewProjectDirPath,
const std::string& aNewProjectName ) :
m_frame( aFrame ),
m_projectDirPath( aSrcProjectDirPath ),
m_projectName( aSrcProjectName ),
m_newProjectDirPath( aNewProjectDirPath ),
m_newProjectName( aNewProjectName )
{
}
virtual wxDirTraverseResult OnFile( const wxString& aSrcFilePath )
{
wxFileName destFile( aSrcFilePath );
wxString ext = destFile.GetExt();
bool atRoot = destFile.GetPath() == m_projectDirPath;
if( ext == "pro" )
{
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 )
m_newProjectFile = destFile;
}
// Currently all paths in the settings file are relative, so we can just do a
// straight copy
CopyFile( aSrcFilePath, destFile.GetFullPath(), m_errors );
}
else if( ext == "sch"
|| ext == "sch-bak"
|| ext == "sym"
|| ext == "lib"
|| ext == "net"
|| destFile.GetName() == "sym-lib-table" )
{
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 == "kicad_pcb"
|| ext == "kicad_pcb-bak"
|| ext == "brd"
|| ext == "kicad_mod"
|| ext == "mod"
|| ext == "cmp"
|| destFile.GetName() == "fp-lib-table" )
{
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 == "kicad_wks" )
{
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 == "gbr"
|| ext == "gbrjob"
|| ext == "drl" )
{
KIFACE* gerbview = m_frame->Kiway().KiFACE( KIWAY::FACE_GERBVIEW );
gerbview->SaveFileAs( m_projectDirPath, m_projectName, m_newProjectDirPath,
m_newProjectName, aSrcFilePath, m_errors );
}
else
{
// Everything we don't recognize just gets a straight copy
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 );
CopyFile( aSrcFilePath, destFile.GetFullPath(), m_errors );
}
/* TODO: what about these?
MacrosFileExtension;
FootprintPlaceFileExtension;
KiCadFootprintLibPathExtension;
GedaPcbFootprintLibFileExtension;
EagleFootprintLibPathExtension;
KiCadLib3DShapesPathExtension;
SpecctraDsnFileExtension;
IpcD356FileExtension;
*/
return wxDIR_CONTINUE;
}
virtual wxDirTraverseResult OnDir( const wxString& dirPath )
{
wxFileName destDir( dirPath );
wxString destDirPath = destDir.GetPath(); // strips off last directory
if( destDirPath.StartsWith( m_projectDirPath ) )
{
destDirPath.Replace( m_projectDirPath, m_newProjectDirPath, false );
destDir.SetPath( destDirPath );
}
if( destDir.GetName() == m_projectName )
destDir.SetName( m_newProjectName );
if( !wxMkdir( destDir.GetFullPath() ) )
{
wxString msg;
if( !m_errors.empty() )
m_errors += "\n";
msg.Printf( _( "Cannot copy file \"%s\"." ), destDir.GetFullPath() );
m_errors += msg;
}
return wxDIR_CONTINUE;
}
wxString GetErrors() { return m_errors; }
wxFileName GetNewProjectFile() { return m_newProjectFile; }
};
int KICAD_MANAGER_CONTROL::SaveProjectAs( const TOOL_EVENT& aEvent )
{
wxString msg;
wxFileName currentProjectFile( Prj().GetProjectFullName() );
wxString currentProjectDirPath = currentProjectFile.GetPath();
wxString currentProjectName = Prj().GetProjectName();
wxString default_dir = m_frame->GetMruPath();
if( default_dir == currentProjectDirPath
|| default_dir == currentProjectDirPath + wxFileName::GetPathSeparator() )
{
// Don't start within the current project
wxFileName default_dir_fn( default_dir );
default_dir_fn.RemoveLastDir();
default_dir = default_dir_fn.GetPath();
}
wxFileDialog dlg( m_frame, _( "Save Project To" ), default_dir, wxEmptyString, wxEmptyString,
wxFD_SAVE );
if( dlg.ShowModal() == wxID_CANCEL )
return -1;
wxFileName newProjectDir( dlg.GetPath() );
if( !newProjectDir.IsAbsolute() )
newProjectDir.MakeAbsolute();
if( wxDirExists( newProjectDir.GetFullPath() ) )
{
msg.Printf( _( "\"%s\" already exists." ), newProjectDir.GetFullPath() );
DisplayErrorMessage( m_frame, msg );
return -1;
}
if( !wxMkdir( newProjectDir.GetFullPath() ) )
{
msg.Printf( _( "Directory \"%s\" could not be created.\n\n"
"Please make sure you have write permissions and try again." ),
newProjectDir.GetPath() );
DisplayErrorMessage( m_frame, msg );
return -1;
}
if( !newProjectDir.IsDirWritable() )
{
msg.Printf( _( "Cannot write to folder \"%s\"." ), newProjectDir.GetFullPath() );
wxMessageDialog msgDlg( m_frame, msg, _( "Error!" ), wxICON_ERROR | wxOK | wxCENTER );
msgDlg.SetExtendedMessage( _( "Please check your access permissions to this folder "
"and try again." ) );
msgDlg.ShowModal();
return -1;
}
const wxString& newProjectDirPath = newProjectDir.GetFullPath();
const wxString& newProjectName = newProjectDir.GetName();
wxDir currentProjectDir( currentProjectDirPath );
SAVE_AS_TRAVERSER traverser( m_frame,
currentProjectDirPath, currentProjectName,
newProjectDirPath, newProjectName );
currentProjectDir.Traverse( traverser );
if( !traverser.GetErrors().empty() )
DisplayErrorMessage( m_frame, traverser.GetErrors() );
if( traverser.GetNewProjectFile().FileExists() )
{
m_frame->CreateNewProject( traverser.GetNewProjectFile() );
m_frame->LoadProject( traverser.GetNewProjectFile() );
}
return 0;
}
int KICAD_MANAGER_CONTROL::Refresh( const TOOL_EVENT& aEvent )
{
m_frame->RefreshProjectTree();
@ -492,6 +728,7 @@ void KICAD_MANAGER_CONTROL::setTransitions()
Go( &KICAD_MANAGER_CONTROL::NewProject, KICAD_MANAGER_ACTIONS::newProject.MakeEvent() );
Go( &KICAD_MANAGER_CONTROL::NewFromTemplate, KICAD_MANAGER_ACTIONS::newFromTemplate.MakeEvent() );
Go( &KICAD_MANAGER_CONTROL::OpenProject, KICAD_MANAGER_ACTIONS::openProject.MakeEvent() );
Go( &KICAD_MANAGER_CONTROL::SaveProjectAs, ACTIONS::saveAs.MakeEvent() );
Go( &KICAD_MANAGER_CONTROL::Refresh, ACTIONS::zoomRedraw.MakeEvent() );
Go( &KICAD_MANAGER_CONTROL::UpdateMenu, ACTIONS::updateMenu.MakeEvent() );

View File

@ -49,6 +49,7 @@ public:
int NewProject( const TOOL_EVENT& aEvent );
int NewFromTemplate( const TOOL_EVENT& aEvent );
int OpenProject( const TOOL_EVENT& aEvent );
int SaveProjectAs( const TOOL_EVENT& aEvent );
int Refresh( const TOOL_EVENT& aEvent );
int UpdateMenu( const TOOL_EVENT& aEvent );

View File

@ -156,7 +156,7 @@ namespace SEXPR
result = "\n";
}
result.append( aLevel* 4, ' ' );
result.append( aLevel * 2, ' ' );
aLevel++;
result += "(";

View File

@ -86,6 +86,16 @@ static struct IFACE : public KIFACE_I
return NULL;
}
/**
* Function SaveFileAs
* Saving a file under a different name is delegated to the various KIFACEs because
* the project doesn't know the internal format of the various files (which may have
* paths in them that need updating).
*/
void SaveFileAs( const std::string& aProjectBasePath, const std::string& aSrcProjectName,
const std::string& aNewProjectBasePath, const std::string& aNewProjectName,
const std::string& aSrcFilePath, std::string& aErrors ) override;
} kiface( "pl_editor", KIWAY::FACE_PL_EDITOR );
} // namespace
@ -124,3 +134,32 @@ void IFACE::OnKifaceEnd()
{
end_common();
}
void IFACE::SaveFileAs( const std::string& aProjectBasePath, const std::string& aSrcProjectName,
const std::string& aNewProjectBasePath, const std::string& aNewProjectName,
const std::string& aSrcFilePath, std::string& aErrors )
{
wxFileName destFile( aSrcFilePath );
wxString destPath = destFile.GetPath();
wxString ext = destFile.GetExt();
if( destPath.StartsWith( aProjectBasePath ) )
{
destPath.Replace( aProjectBasePath, aNewProjectBasePath, false );
destFile.SetPath( destPath );
}
if( ext == "kicad_wks" )
{
if( destFile.GetName() == aSrcProjectName )
destFile.SetName( aNewProjectName );
CopyFile( aSrcFilePath, destFile.GetFullPath(), aErrors );
}
else
{
wxFAIL_MSG( "Unexpected filetype for Pcbnew::SaveFileAs()" );
}
}

View File

@ -40,13 +40,10 @@
#include <macros.h>
#include <pcb_edit_frame.h>
#include <eda_dde.h>
#include <wx/stdpaths.h>
#include <wx/file.h>
#include <wx/snglinst.h>
#include <wx/dir.h>
#include <gestfich.h>
#include <pcbnew.h>
#include <wildcards_and_files_ext.h>
#include <class_board.h>
#include <class_draw_panel_gal.h>
#include <fp_lib_table.h>
@ -55,7 +52,6 @@
#include <footprint_wizard_frame.h>
#include <footprint_preview_panel.h>
#include <footprint_info_impl.h>
#include <gl_context_mgr.h>
#include <dialog_configure_paths.h>
#include "invoke_pcb_dialog.h"
#include "dialog_global_fp_lib_table_config.h"
@ -165,6 +161,16 @@ static struct IFACE : public KIFACE_I
}
}
/**
* Function SaveFileAs
* Saving a file under a different name is delegated to the various KIFACEs because
* the project doesn't know the internal format of the various files (which may have
* paths in them that need updating).
*/
void SaveFileAs( const std::string& aProjectBasePath, const std::string& aSrcProjectName,
const std::string& aNewProjectBasePath, const std::string& aNewProjectName,
const std::string& aSrcFilePath, std::string& aErrors ) override;
} kiface( "pcbnew", KIWAY::FACE_PCB );
} // namespace
@ -378,3 +384,90 @@ void IFACE::OnKifaceEnd()
end_common();
}
void IFACE::SaveFileAs( const std::string& aProjectBasePath, const std::string& aSrcProjectName,
const std::string& aNewProjectBasePath, const std::string& aNewProjectName,
const std::string& aSrcFilePath, std::string& aErrors )
{
wxFileName destFile( aSrcFilePath );
wxString destPath = destFile.GetPath();
wxString ext = destFile.GetExt();
if( destPath.StartsWith( aProjectBasePath ) )
{
destPath.Replace( aProjectBasePath, aNewProjectBasePath, false );
destFile.SetPath( destPath );
}
if( ext == "kicad_pcb" || ext == "kicad_pcb-bak" )
{
if( destFile.GetName() == aSrcProjectName )
destFile.SetName( aNewProjectName );
// JEY TODO: are there filepaths in a PCB file that need updating?
CopyFile( aSrcFilePath, destFile.GetFullPath(), aErrors );
}
else if( ext == "brd" )
{
if( destFile.GetName() == aSrcProjectName )
destFile.SetName( aNewProjectName );
// JEY TODO: are there filepaths in a legacy PCB file that need updating?
CopyFile( aSrcFilePath, destFile.GetFullPath(), aErrors );
}
else if( ext == "mod" || ext == "kicad_mod" )
{
// Footprints are not project-specific. Keep their source names.
CopyFile( aSrcFilePath, destFile.GetFullPath(), aErrors );
}
else if( ext == "cmp" )
{
// JEY TODO
}
else if( ext == "rpt" )
{
// DRC must be the "gold standard". Since we can't gaurantee that there aren't
// any non-deterministic cases in the save-as algorithm, we don't want to certify
// the result with the source's DRC report. Therefore copy it under the old
// name.
CopyFile( aSrcFilePath, destFile.GetFullPath(), aErrors );
}
else if( destFile.GetName() == "fp-lib-table" )
{
try
{
FP_LIB_TABLE fpLibTable;
fpLibTable.Load( aSrcFilePath );
for( int i = 0; i < fpLibTable.GetCount(); i++ )
{
LIB_TABLE_ROW& row = fpLibTable.At( i );
wxString uri = row.GetFullURI();
uri.Replace( "/" + aSrcProjectName + ".pretty", "/" + aNewProjectName + ".pretty" );
row.SetFullURI( uri );
}
fpLibTable.Save( destFile.GetFullPath() );
}
catch( ... )
{
wxString msg;
if( !aErrors.empty() )
aErrors += "\n";
msg.Printf( _( "Cannot copy file \"%s\"." ), destFile.GetFullPath() );
aErrors += msg;
}
}
else
{
wxFAIL_MSG( "Unexpected filetype for Pcbnew::SaveFileAs()" );
}
}