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:
parent
d6e0ec2f24
commit
b5904b0401
|
@ -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( "\"" );
|
||||
|
|
|
@ -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}
|
||||
)
|
||||
|
|
|
@ -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()" );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()" );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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( const auto& kv : values )
|
||||
for( int i : { 1, 2 } )
|
||||
{
|
||||
if( !first )
|
||||
out += ", ";
|
||||
for( const auto& kv : values )
|
||||
{
|
||||
if( i == 1 && kv.first != "Header" )
|
||||
continue;
|
||||
else if( i == 2 && kv.first == "Header" )
|
||||
continue;
|
||||
|
||||
dump( kv.first, out );
|
||||
out += ": ";
|
||||
kv.second.dump( out );
|
||||
first = false;
|
||||
if( !first )
|
||||
out += ",\n";
|
||||
|
||||
dump( kv.first, level + 1, out );
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -208,8 +208,24 @@ struct KIFACE
|
|||
* and old school cast. dynamic_cast is problematic since it needs typeinfo probably
|
||||
* not contained in the caller's link image.
|
||||
*/
|
||||
VTBL_ENTRY wxWindow* CreateWindow( wxWindow* aParent, int aClassId,
|
||||
KIWAY* aKIWAY, int aCtlBits = 0 ) = 0;
|
||||
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
|
||||
|
|
|
@ -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..." ),
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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() );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -156,7 +156,7 @@ namespace SEXPR
|
|||
result = "\n";
|
||||
}
|
||||
|
||||
result.append( aLevel* 4, ' ' );
|
||||
result.append( aLevel * 2, ' ' );
|
||||
aLevel++;
|
||||
result += "(";
|
||||
|
||||
|
|
|
@ -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()" );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()" );
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue