mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 10:13:19 +02:00
Put the string manipuuation utils in the cpp, and remove string_utils.h from the includes of netinfo.h. This spams that header into about 350 files, not all of which need it. Then go round and tidy up the places (most exporters and dialogs) where CPP files weren't including string_utils.h when they used it, as well as some other order-sensitive include issues that turned up.
551 lines
18 KiB
C++
551 lines
18 KiB
C++
/*
|
|
* 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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "dialogs/dialog_export_odbpp.h"
|
|
|
|
#include <set>
|
|
#include <vector>
|
|
#include <thread_pool.h>
|
|
|
|
#include <wx/dir.h>
|
|
#include <wx/filedlg.h>
|
|
#include <wx/wfstream.h>
|
|
#include <wx/zipstrm.h>
|
|
#include <wx/tarstrm.h>
|
|
#include <wx/zstream.h>
|
|
|
|
#include <board.h>
|
|
#include <confirm.h>
|
|
#include <footprint.h>
|
|
#include <kidialog.h>
|
|
#include <kiway_holder.h>
|
|
#include <paths.h>
|
|
#include <pcb_edit_frame.h>
|
|
#include <pcbnew_settings.h>
|
|
#include <pgm_base.h>
|
|
#include <progress_reporter.h>
|
|
#include <project.h>
|
|
#include <project/project_file.h>
|
|
#include <settings/settings_manager.h>
|
|
#include <string_utils.h>
|
|
#include <widgets/std_bitmap_button.h>
|
|
#include <io/io_mgr.h>
|
|
#include <jobs/job_export_pcb_odb.h>
|
|
#include <pcb_io/pcb_io_mgr.h>
|
|
|
|
|
|
|
|
DIALOG_EXPORT_ODBPP::DIALOG_EXPORT_ODBPP( PCB_EDIT_FRAME* aParent ) :
|
|
DIALOG_EXPORT_ODBPP_BASE( aParent ),
|
|
m_parent( aParent ),
|
|
m_job( nullptr )
|
|
{
|
|
m_browseButton->SetBitmap( KiBitmapBundle( BITMAPS::small_folder ) );
|
|
|
|
SetupStandardButtons();
|
|
|
|
// DIALOG_SHIM needs a unique hash_key because classname will be the same for both job and
|
|
// non-job versions.
|
|
m_hash_key = TO_UTF8( GetTitle() );
|
|
|
|
// Now all widgets have the size fixed, call FinishDialogSettings
|
|
finishDialogSettings();
|
|
}
|
|
|
|
|
|
DIALOG_EXPORT_ODBPP::DIALOG_EXPORT_ODBPP( JOB_EXPORT_PCB_ODB* aJob, PCB_EDIT_FRAME* aEditFrame,
|
|
wxWindow* aParent ) :
|
|
DIALOG_EXPORT_ODBPP_BASE( aParent ),
|
|
m_parent( aEditFrame ),
|
|
m_job( aJob )
|
|
{
|
|
m_browseButton->Hide();
|
|
|
|
SetupStandardButtons();
|
|
|
|
// DIALOG_SHIM needs a unique hash_key because classname will be the same for both job and
|
|
// non-job versions.
|
|
m_hash_key = TO_UTF8( GetTitle() );
|
|
|
|
// Now all widgets have the size fixed, call FinishDialogSettings
|
|
finishDialogSettings();
|
|
}
|
|
|
|
|
|
bool DIALOG_EXPORT_ODBPP::TransferDataToWindow()
|
|
{
|
|
if( !m_job )
|
|
{
|
|
if( m_outputFileName->GetValue().IsEmpty() )
|
|
{
|
|
wxFileName brdFile( m_parent->GetBoard()->GetFileName() );
|
|
wxFileName odbFile( brdFile.GetPath(), wxString::Format( wxS( "%s-odb" ), brdFile.GetName() ),
|
|
FILEEXT::ArchiveFileExtension );
|
|
|
|
m_outputFileName->SetValue( odbFile.GetFullPath() );
|
|
OnFmtChoiceOptionChanged();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SetTitle( m_job->GetSettingsDialogTitle() );
|
|
|
|
m_choiceUnits->SetSelection( static_cast<int>( m_job->m_units ) );
|
|
m_precision->SetValue( m_job->m_precision );
|
|
m_choiceCompress->SetSelection( static_cast<int>( m_job->m_compressionMode ) );
|
|
m_outputFileName->SetValue( m_job->GetConfiguredOutputPath() );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void DIALOG_EXPORT_ODBPP::onBrowseClicked( wxCommandEvent& event )
|
|
{
|
|
// clang-format off
|
|
wxString filter = _( "zip files" )
|
|
+ AddFileExtListToFilter( { FILEEXT::ArchiveFileExtension } ) + "|"
|
|
+ _( "tgz files" )
|
|
+ AddFileExtListToFilter( { "tgz" } );
|
|
// clang-format on
|
|
|
|
// Build the absolute path of current output directory to preselect it in the file browser.
|
|
wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() );
|
|
wxFileName fn( Prj().AbsolutePath( path ) );
|
|
|
|
wxFileName brdFile( m_parent->GetBoard()->GetFileName() );
|
|
|
|
wxString fileDialogName( wxString::Format( wxS( "%s-odb" ), brdFile.GetName() ) );
|
|
|
|
wxFileDialog dlg( this, _( "Export ODB++ File" ), fn.GetPath(), fileDialogName, filter, wxFD_SAVE );
|
|
|
|
if( dlg.ShowModal() == wxID_CANCEL )
|
|
return;
|
|
|
|
path = dlg.GetPath();
|
|
|
|
fn = wxFileName( path );
|
|
|
|
if( fn.GetExt().Lower() == "zip" )
|
|
{
|
|
m_choiceCompress->SetSelection( static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::ZIP ) );
|
|
}
|
|
else if( fn.GetExt().Lower() == "tgz" )
|
|
{
|
|
m_choiceCompress->SetSelection( static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ ) );
|
|
}
|
|
else if( path.EndsWith( "/" ) || path.EndsWith( "\\" ) )
|
|
{
|
|
m_choiceCompress->SetSelection( static_cast<int>( JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE ) );
|
|
}
|
|
else
|
|
{
|
|
DisplayErrorMessage( this, _( "The selected output file name is not a supported archive format." ) );
|
|
return;
|
|
}
|
|
|
|
m_outputFileName->SetValue( path );
|
|
}
|
|
|
|
|
|
void DIALOG_EXPORT_ODBPP::onFormatChoice( wxCommandEvent& event )
|
|
{
|
|
OnFmtChoiceOptionChanged();
|
|
}
|
|
|
|
|
|
void DIALOG_EXPORT_ODBPP::OnFmtChoiceOptionChanged()
|
|
{
|
|
wxString fn = m_outputFileName->GetValue();
|
|
|
|
wxFileName fileName( fn );
|
|
|
|
auto compressionMode = static_cast<JOB_EXPORT_PCB_ODB::ODB_COMPRESSION>( m_choiceCompress->GetSelection() );
|
|
|
|
int sepIdx = std::max( fn.Find( '/', true ), fn.Find( '\\', true ) );
|
|
int dotIdx = fn.Find( '.', true );
|
|
|
|
if( fileName.IsDir() )
|
|
fn = fn.Mid( 0, sepIdx );
|
|
else if( sepIdx < dotIdx )
|
|
fn = fn.Mid( 0, dotIdx );
|
|
|
|
switch( compressionMode )
|
|
{
|
|
case JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::ZIP:
|
|
fn = fn + '.' + FILEEXT::ArchiveFileExtension;
|
|
break;
|
|
case JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ:
|
|
fn += ".tgz";
|
|
break;
|
|
case JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE:
|
|
fn = wxFileName( fn, "" ).GetFullPath();
|
|
break;
|
|
default:
|
|
break;
|
|
};
|
|
|
|
m_outputFileName->SetValue( fn );
|
|
}
|
|
|
|
void DIALOG_EXPORT_ODBPP::onOKClick( wxCommandEvent& event )
|
|
{
|
|
if( !m_job )
|
|
{
|
|
wxString fn = m_outputFileName->GetValue();
|
|
|
|
if( fn.IsEmpty() )
|
|
{
|
|
DisplayErrorMessage( this, _( "Output file name cannot be empty." ) );
|
|
return;
|
|
}
|
|
|
|
auto compressionMode = static_cast<JOB_EXPORT_PCB_ODB::ODB_COMPRESSION>( m_choiceCompress->GetSelection() );
|
|
|
|
wxFileName fileName( fn );
|
|
bool isDirectory = fileName.IsDir();
|
|
wxString extension = fileName.GetExt();
|
|
|
|
if( ( compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE && !isDirectory )
|
|
|| ( compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::ZIP && extension != "zip" )
|
|
|| ( compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ && extension != "tgz" ) )
|
|
{
|
|
DisplayErrorMessage( this, _( "The output file name conflicts with the selected compression format." ) );
|
|
return;
|
|
}
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
|
|
bool DIALOG_EXPORT_ODBPP::TransferDataFromWindow()
|
|
{
|
|
if( m_job )
|
|
{
|
|
m_job->SetConfiguredOutputPath( m_outputFileName->GetValue() );
|
|
|
|
m_job->m_precision = m_precision->GetValue();
|
|
m_job->m_units = static_cast<JOB_EXPORT_PCB_ODB::ODB_UNITS>( m_choiceUnits->GetSelection() );
|
|
m_job->m_compressionMode = static_cast<JOB_EXPORT_PCB_ODB::ODB_COMPRESSION>( m_choiceCompress->GetSelection() );
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void DIALOG_EXPORT_ODBPP::GenerateODBPPFiles( const JOB_EXPORT_PCB_ODB& aJob, BOARD* aBoard,
|
|
PCB_EDIT_FRAME* aParentFrame, PROGRESS_REPORTER* aProgressReporter,
|
|
REPORTER* aReporter )
|
|
{
|
|
wxCHECK( aBoard, /* void */ );
|
|
wxString outputPath = aJob.GetFullOutputPath( aBoard->GetProject() );
|
|
|
|
if( outputPath.IsEmpty() )
|
|
outputPath = wxFileName( aJob.m_filename ).GetPath();
|
|
|
|
wxFileName outputFn( outputPath );
|
|
|
|
// Write through symlinks, don't replace them
|
|
WX_FILENAME::ResolvePossibleSymlinks( outputFn );
|
|
|
|
if( outputFn.GetPath().IsEmpty() && outputFn.HasName() )
|
|
outputFn.MakeAbsolute();
|
|
|
|
bool outputIsSingleFile = aJob.m_compressionMode != JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::NONE;
|
|
wxString msg;
|
|
|
|
if( !PATHS::EnsurePathExists( outputFn.GetPath(), outputIsSingleFile ) )
|
|
{
|
|
msg.Printf( _( "Cannot create output directory '%s'." ), outputFn.GetFullPath() );
|
|
|
|
if( aReporter )
|
|
aReporter->Report( msg, RPT_SEVERITY_ERROR );
|
|
|
|
return;
|
|
}
|
|
|
|
if( outputFn.IsDir() && !outputFn.IsDirWritable() )
|
|
{
|
|
msg.Printf( _( "Insufficient permissions to folder '%s'." ), outputFn.GetPath() );
|
|
|
|
if( aReporter )
|
|
aReporter->Report( msg, RPT_SEVERITY_ERROR );
|
|
|
|
return;
|
|
}
|
|
|
|
if( outputIsSingleFile )
|
|
{
|
|
bool writeable = outputFn.FileExists() ? outputFn.IsFileWritable() : outputFn.IsDirWritable();
|
|
|
|
if( !writeable )
|
|
{
|
|
msg.Printf( _( "Insufficient permissions to save file '%s'." ), outputFn.GetFullPath() );
|
|
|
|
if( aReporter )
|
|
aReporter->Report( msg, RPT_SEVERITY_ERROR );
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
wxFileName tempFile( outputFn.GetFullPath() );
|
|
|
|
if( outputIsSingleFile )
|
|
{
|
|
if( outputFn.Exists() )
|
|
{
|
|
if( aParentFrame )
|
|
{
|
|
msg = wxString::Format( _( "Output files '%s' already exists. Do you want to overwrite it?" ),
|
|
outputFn.GetFullPath() );
|
|
|
|
KIDIALOG errorDlg( aParentFrame, msg, _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
|
|
errorDlg.SetOKLabel( _( "Overwrite" ) );
|
|
|
|
if( errorDlg.ShowModal() != wxID_OK )
|
|
return;
|
|
|
|
if( !wxRemoveFile( outputFn.GetFullPath() ) )
|
|
{
|
|
msg.Printf( _( "Cannot remove existing output file '%s'." ), outputFn.GetFullPath() );
|
|
DisplayErrorMessage( aParentFrame, msg );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
msg = wxString::Format( _( "Output file '%s' already exists." ), outputFn.GetFullPath() );
|
|
|
|
if( aReporter )
|
|
aReporter->Report( msg, RPT_SEVERITY_ERROR );
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
tempFile.AssignDir( wxFileName::GetTempDir() );
|
|
tempFile.AppendDir( "kicad" );
|
|
tempFile.AppendDir( "odb" );
|
|
|
|
if( !wxFileName::Mkdir( tempFile.GetFullPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
|
|
{
|
|
msg.Printf( _( "Cannot create temporary output directory." ) );
|
|
|
|
if( aReporter )
|
|
aReporter->Report( msg, RPT_SEVERITY_ERROR );
|
|
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Test for the output directory of tempFile
|
|
wxDir testDir( tempFile.GetFullPath() );
|
|
|
|
if( testDir.IsOpened() && ( testDir.HasFiles() || testDir.HasSubDirs() ) )
|
|
{
|
|
if( aParentFrame )
|
|
{
|
|
msg = wxString::Format( _( "Output directory '%s' already exists and is not empty. "
|
|
"Do you want to overwrite it?" ),
|
|
tempFile.GetFullPath() );
|
|
|
|
KIDIALOG errorDlg( aParentFrame, msg, _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
|
|
errorDlg.SetOKLabel( _( "Overwrite" ) );
|
|
|
|
if( errorDlg.ShowModal() != wxID_OK )
|
|
return;
|
|
|
|
if( !tempFile.Rmdir( wxPATH_RMDIR_RECURSIVE ) )
|
|
{
|
|
msg.Printf( _( "Cannot remove existing output directory '%s'." ), tempFile.GetFullPath() );
|
|
DisplayErrorMessage( aParentFrame, msg );
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
msg = wxString::Format( _( "Output directory '%s' already exists." ), tempFile.GetFullPath() );
|
|
|
|
if( aReporter )
|
|
aReporter->Report( msg, RPT_SEVERITY_ERROR );
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
std::map<std::string, UTF8> props;
|
|
|
|
props["units"] = aJob.m_units == JOB_EXPORT_PCB_ODB::ODB_UNITS::MM ? "mm" : "inch";
|
|
props["sigfig"] = wxString::Format( "%d", aJob.m_precision );
|
|
|
|
auto saveFile =
|
|
[&]() -> bool
|
|
{
|
|
try
|
|
{
|
|
IO_RELEASER<PCB_IO> pi( PCB_IO_MGR::PluginFind( PCB_IO_MGR::ODBPP ) );
|
|
pi->SetReporter( aReporter );
|
|
pi->SetProgressReporter( aProgressReporter );
|
|
pi->SaveBoard( tempFile.GetFullPath(), aBoard, &props );
|
|
return true;
|
|
}
|
|
catch( const IO_ERROR& ioe )
|
|
{
|
|
if( aReporter )
|
|
{
|
|
msg = wxString::Format( _( "Error generating ODBPP files '%s'.\n%s" ),
|
|
tempFile.GetFullPath(), ioe.What() );
|
|
aReporter->Report( msg, RPT_SEVERITY_ERROR );
|
|
}
|
|
|
|
// In case we started a file but didn't fully write it, clean up
|
|
wxFileName::Rmdir( tempFile.GetFullPath() );
|
|
return false;
|
|
}
|
|
};
|
|
|
|
thread_pool& tp = GetKiCadThreadPool();
|
|
auto ret = tp.submit( saveFile );
|
|
|
|
std::future_status status = ret.wait_for( std::chrono::milliseconds( 250 ) );
|
|
|
|
while( status != std::future_status::ready )
|
|
{
|
|
if( aProgressReporter )
|
|
aProgressReporter->KeepRefreshing();
|
|
|
|
status = ret.wait_for( std::chrono::milliseconds( 250 ) );
|
|
}
|
|
|
|
try
|
|
{
|
|
if( !ret.get() )
|
|
return;
|
|
}
|
|
catch( const std::exception& e )
|
|
{
|
|
if( aReporter )
|
|
{
|
|
aReporter->Report( wxString::Format( "Exception in ODB++ generation: %s", e.what() ),
|
|
RPT_SEVERITY_ERROR );
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
if( aJob.m_compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::ZIP )
|
|
{
|
|
if( aProgressReporter )
|
|
aProgressReporter->AdvancePhase( _( "Compressing output" ) );
|
|
|
|
wxFFileOutputStream fnout( outputFn.GetFullPath() );
|
|
wxZipOutputStream zipStream( fnout );
|
|
|
|
std::function<void( const wxString&, const wxString& )> addDirToZip =
|
|
[&]( const wxString& dirPath, const wxString& parentPath )
|
|
{
|
|
wxDir dir( dirPath );
|
|
wxString fileName;
|
|
|
|
bool cont = dir.GetFirst( &fileName, wxEmptyString, wxDIR_DEFAULT );
|
|
|
|
while( cont )
|
|
{
|
|
wxFileName fileInZip( dirPath, fileName );
|
|
wxString relativePath = fileName;
|
|
|
|
if( !parentPath.IsEmpty() )
|
|
relativePath = parentPath + wxString( wxFileName::GetPathSeparator() ) + fileName;
|
|
|
|
if( wxFileName::DirExists( fileInZip.GetFullPath() ) )
|
|
{
|
|
zipStream.PutNextDirEntry( relativePath );
|
|
addDirToZip( fileInZip.GetFullPath(), relativePath );
|
|
}
|
|
else
|
|
{
|
|
wxFFileInputStream fileStream( fileInZip.GetFullPath() );
|
|
zipStream.PutNextEntry( relativePath );
|
|
fileStream.Read( zipStream );
|
|
}
|
|
cont = dir.GetNext( &fileName );
|
|
}
|
|
};
|
|
|
|
addDirToZip( tempFile.GetFullPath(), wxEmptyString );
|
|
|
|
zipStream.Close();
|
|
fnout.Close();
|
|
|
|
tempFile.Rmdir( wxPATH_RMDIR_RECURSIVE );
|
|
}
|
|
else if( aJob.m_compressionMode == JOB_EXPORT_PCB_ODB::ODB_COMPRESSION::TGZ )
|
|
{
|
|
wxFFileOutputStream fnout( outputFn.GetFullPath() );
|
|
wxZlibOutputStream zlibStream( fnout, -1, wxZLIB_GZIP );
|
|
wxTarOutputStream tarStream( zlibStream );
|
|
|
|
std::function<void( const wxString&, const wxString& )> addDirToTar =
|
|
[&]( const wxString& dirPath, const wxString& parentPath )
|
|
{
|
|
wxDir dir( dirPath );
|
|
wxString fileName;
|
|
|
|
bool cont = dir.GetFirst( &fileName, wxEmptyString, wxDIR_DEFAULT );
|
|
while( cont )
|
|
{
|
|
wxFileName fileInTar( dirPath, fileName );
|
|
wxString relativePath = fileName;
|
|
|
|
if( !parentPath.IsEmpty() )
|
|
relativePath = parentPath + wxString( wxFileName::GetPathSeparator() ) + fileName;
|
|
|
|
if( wxFileName::DirExists( fileInTar.GetFullPath() ) )
|
|
{
|
|
tarStream.PutNextDirEntry( relativePath );
|
|
addDirToTar( fileInTar.GetFullPath(), relativePath );
|
|
}
|
|
else
|
|
{
|
|
wxFFileInputStream fileStream( fileInTar.GetFullPath() );
|
|
tarStream.PutNextEntry( relativePath, wxDateTime::Now(), fileStream.GetLength() );
|
|
fileStream.Read( tarStream );
|
|
}
|
|
cont = dir.GetNext( &fileName );
|
|
}
|
|
};
|
|
|
|
addDirToTar( tempFile.GetFullPath(),
|
|
tempFile.GetPath( wxPATH_NO_SEPARATOR ).AfterLast( tempFile.GetPathSeparator() ) );
|
|
|
|
tarStream.Close();
|
|
zlibStream.Close();
|
|
fnout.Close();
|
|
|
|
tempFile.Rmdir( wxPATH_RMDIR_RECURSIVE );
|
|
}
|
|
|
|
if( aProgressReporter )
|
|
aProgressReporter->SetCurrentProgress( 1 );
|
|
}
|