kicad/eeschema/dialogs/dialog_symbol_remap.cpp

529 lines
19 KiB
C++
Raw Normal View History

/**
* @file dialog_symbol_remap.cpp
*/
/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 Wayne Stambaugh <stambaughw@gmail.com>
* Copyright (C) 2017-2023 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 <macros.h>
#include <pgm_base.h>
2021-09-14 22:45:14 +00:00
#include <kiface_base.h>
#include <project.h>
#include <confirm.h>
#include <reporter.h>
#include <wildcards_and_files_ext.h>
#include <wx_filename.h>
#include "widgets/wx_html_report_panel.h"
#include <symbol_library.h>
#include <core/kicad_algo.h>
2020-12-25 23:37:01 +00:00
#include <symbol_viewer_frame.h>
#include <project_rescue.h>
#include <sch_io/sch_io_mgr.h>
#include <sch_symbol.h>
#include <sch_screen.h>
2018-01-30 10:49:51 +00:00
#include <sch_edit_frame.h>
#include <schematic.h>
#include <settings/settings_manager.h>
#include <symbol_lib_table.h>
#include <env_paths.h>
#include <project_sch.h>
#include <dialog_symbol_remap.h>
DIALOG_SYMBOL_REMAP::DIALOG_SYMBOL_REMAP( SCH_EDIT_FRAME* aParent ) :
DIALOG_SYMBOL_REMAP_BASE( aParent ),
m_frame( aParent )
{
m_remapped = false;
wxString projectPath = Prj().GetProjectPath();
if( !wxFileName::IsDirWritable( projectPath ) )
{
wxString msg =
wxString::Format( _( "Remapping is not possible because you have insufficient "
"privileges to the project folder '%s'." ),
projectPath );
DisplayInfoMessage( this, msg );
// Disable the remap button.
m_remapped = true;
}
wxString text;
text = _( "This schematic currently uses the project symbol library list look up method "
"for loading library symbols. KiCad will attempt to map the existing symbols "
"to use the new symbol library table. Remapping will change some project files "
"and schematics may not be compatible with older versions of KiCad. All files "
"that are changed will be backed up to the \"rescue-backup\" folder in the project "
"folder should you need to revert any changes. If you choose to skip this step, "
"you will be responsible for manually remapping the symbols." );
m_htmlCtrl->AppendToPage( text );
m_messagePanel->SetFileName( Prj().GetProjectPath() + wxT( "report.txt" ) );
}
void DIALOG_SYMBOL_REMAP::OnRemapSymbols( wxCommandEvent& aEvent )
{
SCH_EDIT_FRAME* parent = dynamic_cast< SCH_EDIT_FRAME* >( GetParent() );
wxCHECK_RET( parent != nullptr, "Parent window is not type SCH_EDIT_FRAME." );
if( !backupProject( m_messagePanel->Reporter() ) )
return;
// Ignore the never show rescue setting for one last rescue of legacy symbol
// libraries before remapping to the symbol library table. This ensures the
// best remapping results.
LEGACY_RESCUER rescuer( Prj(), &parent->Schematic(), &parent->GetCurrentSheet(),
parent->GetCanvas()->GetBackend() );
if( RESCUER::RescueProject( this, rescuer, false ) )
{
wxBusyCursor busy;
2020-12-25 23:37:01 +00:00
auto viewer = (SYMBOL_VIEWER_FRAME*) parent->Kiway().Player( FRAME_SCH_VIEWER, false );
if( viewer )
2020-12-17 23:56:47 +00:00
viewer->ReCreateLibList();
2020-07-13 13:50:35 +00:00
parent->ClearUndoORRedoList( EDA_BASE_FRAME::UNDO_LIST, 1 );
parent->SyncView();
parent->GetCanvas()->Refresh();
parent->OnModify();
}
// The schematic is fully loaded, any legacy library symbols have been rescued. Now
// check to see if the schematic has not been converted to the symbol library table
// method for looking up symbols.
wxFileName prjSymLibTableFileName( Prj().GetProjectPath(),
SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
// Delete the existing project symbol library table.
if( prjSymLibTableFileName.FileExists() )
wxRemoveFile( prjSymLibTableFileName.GetFullPath() );
createProjectSymbolLibTable( m_messagePanel->Reporter() );
2021-07-16 20:13:26 +00:00
Prj().SetElem( PROJECT::ELEM_SYMBOL_LIB_TABLE, nullptr );
PROJECT_SCH::SchSymbolLibTable( &Prj() );
remapSymbolsToLibTable( m_messagePanel->Reporter() );
// Remove all of the libraries from the legacy library list.
wxString paths;
wxArrayString libNames;
SYMBOL_LIBS::SetLibNamesAndPaths( &Prj(), paths, libNames );
// Reload the cache symbol library.
2021-07-16 20:13:26 +00:00
Prj().SetElem( PROJECT::ELEM_SCH_SYMBOL_LIBS, nullptr );
PROJECT_SCH::SchLibs( &Prj() );
Raise();
m_remapped = true;
}
size_t DIALOG_SYMBOL_REMAP::getLibsNotInGlobalSymbolLibTable( std::vector< SYMBOL_LIB* >& aLibs )
{
SYMBOL_LIBS* libs = PROJECT_SCH::SchLibs( &Prj() );
for( SYMBOL_LIBS_BASE::iterator it = libs->begin(); it != libs->end(); ++it )
{
// Ignore the cache library.
if( it->IsCache() )
continue;
// Check for the obvious library name.
wxString libFileName = it->GetFullFileName();
if( !SYMBOL_LIB_TABLE::GetGlobalLibTable().FindRowByURI( libFileName ) )
aLibs.push_back( &(*it) );
}
return aLibs.size();
}
void DIALOG_SYMBOL_REMAP::createProjectSymbolLibTable( REPORTER& aReporter )
{
std::vector<SYMBOL_LIB*> libs;
if( getLibsNotInGlobalSymbolLibTable( libs ) )
{
wxBusyCursor busy;
SYMBOL_LIB_TABLE libTable;
std::vector<wxString> libNames = SYMBOL_LIB_TABLE::GetGlobalLibTable().GetLogicalLibs();
wxString msg;
for( SYMBOL_LIB* lib : libs )
{
wxString libName = lib->GetName();
int libNameInc = 1;
int libNameLen = libName.Length();
// Spaces in the file name will break the symbol name because they are not
// quoted in the symbol library file format.
2023-01-17 04:14:38 +00:00
libName.Replace( wxS( " " ), wxS( "-" ) );
// Don't create duplicate table entries.
2020-09-26 13:42:40 +00:00
while( alg::contains( libNames, libName ) )
{
libName = libName.Left( libNameLen );
libName << libNameInc;
libNameInc++;
}
wxString type = SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_LEGACY );
wxFileName fn( lib->GetFullFileName() );
// Use environment variable substitution where possible. This is based solely
// on the internal user environment variable list. Checking against all of the
// system wide environment variables is probably not a good idea.
wxString normalizedPath = NormalizePath( fn, &Pgm().GetLocalEnvVariables(), &Prj() );
wxFileName normalizedFn( normalizedPath );
// Don't add symbol libraries that do not exist.
if( normalizedFn.Normalize(FN_NORMALIZE_FLAGS | wxPATH_NORM_ENV_VARS )
&& normalizedFn.FileExists() )
{
msg.Printf( _( "Adding library '%s', file '%s' to project symbol library table." ),
libName,
normalizedPath );
aReporter.Report( msg, RPT_SEVERITY_INFO );
libTable.InsertRow( new SYMBOL_LIB_TABLE_ROW( libName, normalizedPath, type ) );
}
else
{
msg.Printf( _( "Library '%s' not found." ), normalizedPath );
aReporter.Report( msg, RPT_SEVERITY_WARNING );
}
}
// Don't save empty project symbol library table.
if( !libTable.IsEmpty() )
{
wxFileName fn( Prj().GetProjectPath(), SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
try
{
FILE_OUTPUTFORMATTER formatter( fn.GetFullPath() );
libTable.Format( &formatter, 0 );
}
catch( const IO_ERROR& ioe )
{
msg.Printf( _( "Error writing project symbol library table.\n %s" ), ioe.What() );
aReporter.ReportTail( msg, RPT_SEVERITY_ERROR );
}
2021-06-10 14:10:55 +00:00
aReporter.ReportTail( _( "Created project symbol library table.\n" ),
RPT_SEVERITY_INFO );
}
}
}
void DIALOG_SYMBOL_REMAP::remapSymbolsToLibTable( REPORTER& aReporter )
{
wxBusyCursor busy;
wxString msg;
SCH_SCREENS schematic( m_frame->Schematic().Root() );
SCH_SYMBOL* symbol;
SCH_SCREEN* screen;
for( screen = schematic.GetFirst(); screen; screen = schematic.GetNext() )
{
2022-07-29 21:02:35 +00:00
for( EDA_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) )
{
2021-06-10 14:10:55 +00:00
symbol = dynamic_cast<SCH_SYMBOL*>( item );
2022-10-17 13:19:39 +00:00
wxCHECK2( symbol, continue );
if( !remapSymbolToLibTable( symbol ) )
{
msg.Printf( _( "No symbol %s found in symbol library table." ),
symbol->GetLibId().GetLibItemName().wx_str() );
aReporter.Report( msg, RPT_SEVERITY_WARNING );
}
else
{
msg.Printf( _( "Symbol %s mapped to symbol library '%s'." ),
symbol->GetLibId().GetLibItemName().wx_str(),
symbol->GetLibId().GetLibNickname().wx_str() );
aReporter.Report( msg, RPT_SEVERITY_ACTION );
screen->SetContentModified();
}
}
}
aReporter.Report( _( "Symbol library table mapping complete!" ), RPT_SEVERITY_INFO );
Make the new schematic and symbol library file formats the default. This is a very large and potentially disruptive change so this will be an unusually long and detailed commit message. The new file formats are now the default in both the schematic and symbol library editors. Existing symbol libraries will be saved in their current format until new features are added to library symbols. Once this happens, both the legacy schematic and symbol file formats will be no longer be savable and existing libraries will have to be converted. Saving to the legacy file formats is still available for round robin testing and should not be used for normal editing. When loading the legacy schematic file, it is imperative that the schematic library symbols are rescued and/or remapped to valid library identifiers. Otherwise, there will be no way to link to the original library symbol and the user will be required manually set the library identifier. The cached symbol will be saved in the schematic file so the last library symbol in the cache will still be used but there will be no way to update it from the original library. The next save after loading a legacy schematic file will be converted to the s-expression file format. Schematics with hierarchical sheets will automatically have all sheet file name extensions changed to .kicad_sym and saved to the new format as well. Appending schematics requires that the schematic to append has already been converted to the new file format. This is required to ensure that library symbols are guaranteed to be valid for the appended schematic. The schematic symbol library symbol link resolution has been moved out of the SCH_COMPONENT object and move into the SCH_SCREEN object that owns the symbol. This was done to ensure that there is a single place where the library symbol links get resolved rather than the dozen or so different code paths that previously existed. It also removes the necessity of the SCH_COMPONENT object of requiring any knowledge of the symbol library table and/or the cache library. When opening an s-expression schematic, the legacy cache library is not loaded so any library symbols not rescued cannot be loaded. Broken library symbol links will have to be manually resolved by adding the cache library to the symbol library table and changing the links in the schematic symbol. Now that the library symbols are embedded in the schematic file, the SCH_SCREEN object maintains the list of library symbols for the schematic automatically. No external manipulation of this library cache should ever occur. ADDED: S-expression schematic and symbol library file formats.
2020-04-16 16:43:50 +00:00
schematic.UpdateSymbolLinks();
}
2021-06-10 14:10:55 +00:00
bool DIALOG_SYMBOL_REMAP::remapSymbolToLibTable( SCH_SYMBOL* aSymbol )
{
2021-07-16 20:13:26 +00:00
wxCHECK_MSG( aSymbol != nullptr, false, "Null pointer passed to remapSymbolToLibTable." );
wxCHECK_MSG( aSymbol->GetLibId().GetLibNickname().empty(), false,
"Cannot remap symbol that is already mapped." );
wxCHECK_MSG( !aSymbol->GetLibId().GetLibItemName().empty(), false,
"The symbol LIB_ID name is empty." );
SYMBOL_LIBS* libs = PROJECT_SCH::SchLibs( &Prj() );
for( SYMBOL_LIBS_BASE::iterator it = libs->begin(); it != libs->end(); ++it )
{
// Ignore the cache library.
if( it->IsCache() )
continue;
LIB_SYMBOL* alias = it->FindSymbol( aSymbol->GetLibId().GetLibItemName().wx_str() );
// Found in the same library as the old look up method assuming the user didn't
// change the libraries or library ordering since the last time the schematic was
// loaded.
if( alias )
{
// Find the same library in the symbol library table using the full path and file name.
wxString libFileName = it->GetFullFileName();
const LIB_TABLE_ROW* row = PROJECT_SCH::SchSymbolLibTable( &Prj() )->FindRowByURI( libFileName );
if( row )
{
LIB_ID id = aSymbol->GetLibId();
id.SetLibNickname( row->GetNickName() );
// Don't resolve symbol library links now.
Make the new schematic and symbol library file formats the default. This is a very large and potentially disruptive change so this will be an unusually long and detailed commit message. The new file formats are now the default in both the schematic and symbol library editors. Existing symbol libraries will be saved in their current format until new features are added to library symbols. Once this happens, both the legacy schematic and symbol file formats will be no longer be savable and existing libraries will have to be converted. Saving to the legacy file formats is still available for round robin testing and should not be used for normal editing. When loading the legacy schematic file, it is imperative that the schematic library symbols are rescued and/or remapped to valid library identifiers. Otherwise, there will be no way to link to the original library symbol and the user will be required manually set the library identifier. The cached symbol will be saved in the schematic file so the last library symbol in the cache will still be used but there will be no way to update it from the original library. The next save after loading a legacy schematic file will be converted to the s-expression file format. Schematics with hierarchical sheets will automatically have all sheet file name extensions changed to .kicad_sym and saved to the new format as well. Appending schematics requires that the schematic to append has already been converted to the new file format. This is required to ensure that library symbols are guaranteed to be valid for the appended schematic. The schematic symbol library symbol link resolution has been moved out of the SCH_COMPONENT object and move into the SCH_SCREEN object that owns the symbol. This was done to ensure that there is a single place where the library symbol links get resolved rather than the dozen or so different code paths that previously existed. It also removes the necessity of the SCH_COMPONENT object of requiring any knowledge of the symbol library table and/or the cache library. When opening an s-expression schematic, the legacy cache library is not loaded so any library symbols not rescued cannot be loaded. Broken library symbol links will have to be manually resolved by adding the cache library to the symbol library table and changing the links in the schematic symbol. Now that the library symbols are embedded in the schematic file, the SCH_SCREEN object maintains the list of library symbols for the schematic automatically. No external manipulation of this library cache should ever occur. ADDED: S-expression schematic and symbol library file formats.
2020-04-16 16:43:50 +00:00
aSymbol->SetLibId( id );
return true;
}
}
}
return false;
}
bool DIALOG_SYMBOL_REMAP::backupProject( REPORTER& aReporter )
{
static wxString backupFolder = "rescue-backup";
wxString tmp;
wxString errorMsg;
wxFileName srcFileName;
wxFileName destFileName;
wxFileName backupPath;
SCH_SCREENS schematic( m_frame->Schematic().Root() );
// Copy backup files to different folder so as not to pollute the project folder.
destFileName.SetPath( Prj().GetProjectPath() );
destFileName.AppendDir( backupFolder );
backupPath = destFileName;
if( !destFileName.DirExists() )
{
if( !destFileName.Mkdir() )
{
errorMsg.Printf( _( "Cannot create project remap back up folder '%s'." ),
destFileName.GetPath() );
wxMessageDialog dlg( this, errorMsg, _( "Backup Error" ),
wxYES_NO | wxCENTRE | wxRESIZE_BORDER | wxICON_QUESTION );
dlg.SetYesNoLabels( wxMessageDialog::ButtonLabel( _( "Continue with Rescue" ) ),
wxMessageDialog::ButtonLabel( _( "Abort Rescue" ) ) );
if( dlg.ShowModal() == wxID_NO )
return false;
}
}
{
wxBusyCursor busy;
// Time stamp to append to file name in case multiple remappings are performed.
wxString timeStamp = wxDateTime::Now().Format( "-%Y-%m-%d-%H-%M-%S" );
// Back up symbol library table.
srcFileName.SetPath( Prj().GetProjectPath() );
srcFileName.SetName( SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
destFileName = srcFileName;
destFileName.AppendDir( backupFolder );
destFileName.SetName( destFileName.GetName() + timeStamp );
tmp.Printf( _( "Backing up file '%s' to '%s'." ),
srcFileName.GetFullPath(),
destFileName.GetFullPath() );
aReporter.Report( tmp, RPT_SEVERITY_INFO );
if( wxFileName::Exists( srcFileName.GetFullPath() )
&& !wxCopyFile( srcFileName.GetFullPath(), destFileName.GetFullPath() ) )
{
tmp.Printf( _( "Failed to back up file '%s'.\n" ),
srcFileName.GetFullPath() );
errorMsg += tmp;
}
// Back up the schematic files.
for( SCH_SCREEN* screen = schematic.GetFirst(); screen; screen = schematic.GetNext() )
{
destFileName = screen->GetFileName();
destFileName.SetName( destFileName.GetName() + timeStamp );
// Check for nest hierarchical schematic paths.
if( destFileName.GetPath() != backupPath.GetPath() )
{
destFileName.SetPath( backupPath.GetPath() );
wxArrayString srcDirs = wxFileName( screen->GetFileName() ).GetDirs();
wxArrayString destDirs = wxFileName( Prj().GetProjectPath() ).GetDirs();
for( size_t i = destDirs.GetCount(); i < srcDirs.GetCount(); i++ )
destFileName.AppendDir( srcDirs[i] );
}
else
{
destFileName.AppendDir( backupFolder );
}
tmp.Printf( _( "Backing up file '%s' to '%s'." ),
screen->GetFileName(),
destFileName.GetFullPath() );
aReporter.Report( tmp, RPT_SEVERITY_INFO );
if( !destFileName.DirExists() && !destFileName.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
{
tmp.Printf( _( "Failed to create backup folder '%s'.\n" ), destFileName.GetPath() );
errorMsg += tmp;
continue;
}
if( wxFileName::Exists( screen->GetFileName() )
&& !wxCopyFile( screen->GetFileName(), destFileName.GetFullPath() ) )
{
tmp.Printf( _( "Failed to back up file '%s'.\n" ), screen->GetFileName() );
errorMsg += tmp;
}
}
// Back up the project file.
destFileName = Prj().GetProjectFullName();
destFileName.SetName( destFileName.GetName() + timeStamp );
destFileName.AppendDir( backupFolder );
tmp.Printf( _( "Backing up file '%s' to '%s'." ),
Prj().GetProjectFullName(),
destFileName.GetFullPath() );
aReporter.Report( tmp, RPT_SEVERITY_INFO );
if( wxFileName::Exists( Prj().GetProjectFullName() )
&& !wxCopyFile( Prj().GetProjectFullName(), destFileName.GetFullPath() ) )
{
tmp.Printf( _( "Failed to back up file '%s'.\n" ), Prj().GetProjectFullName() );
errorMsg += tmp;
}
// Back up the cache library.
srcFileName.SetPath( Prj().GetProjectPath() );
2023-01-17 04:14:38 +00:00
srcFileName.SetName( Prj().GetProjectName() + wxS( "-cache" ) );
srcFileName.SetExt( FILEEXT::LegacySymbolLibFileExtension );
destFileName = srcFileName;
destFileName.SetName( destFileName.GetName() + timeStamp );
destFileName.AppendDir( backupFolder );
tmp.Printf( _( "Backing up file '%s' to '%s'." ),
srcFileName.GetFullPath(),
destFileName.GetFullPath() );
aReporter.Report( tmp, RPT_SEVERITY_INFO );
if( srcFileName.Exists()
&& !wxCopyFile( srcFileName.GetFullPath(), destFileName.GetFullPath() ) )
{
tmp.Printf( _( "Failed to back up file '%s'.\n" ), srcFileName.GetFullPath() );
errorMsg += tmp;
}
// Back up the rescue symbol library if it exists.
2023-01-17 04:14:38 +00:00
srcFileName.SetName( Prj().GetProjectName() + wxS( "-rescue" ) );
destFileName.SetName( srcFileName.GetName() + timeStamp );
tmp.Printf( _( "Backing up file '%s' to '%s'." ),
srcFileName.GetFullPath(),
destFileName.GetFullPath() );
aReporter.Report( tmp, RPT_SEVERITY_INFO );
if( srcFileName.Exists()
&& !wxCopyFile( srcFileName.GetFullPath(), destFileName.GetFullPath() ) )
{
tmp.Printf( _( "Failed to back up file '%s'.\n" ), srcFileName.GetFullPath() );
errorMsg += tmp;
}
// Back up the rescue symbol library document file if it exists.
srcFileName.SetExt( FILEEXT::LegacySymbolDocumentFileExtension );
destFileName.SetExt( srcFileName.GetExt() );
tmp.Printf( _( "Backing up file '%s' to '%s'." ),
srcFileName.GetFullPath(),
destFileName.GetFullPath() );
aReporter.Report( tmp, RPT_SEVERITY_INFO );
if( srcFileName.Exists()
&& !wxCopyFile( srcFileName.GetFullPath(), destFileName.GetFullPath() ) )
{
tmp.Printf( _( "Failed to back up file '%s'.\n" ), srcFileName.GetFullPath() );
errorMsg += tmp;
}
}
if( !errorMsg.IsEmpty() )
{
wxMessageDialog dlg( this, _( "Some of the project files could not be backed up." ),
_( "Backup Error" ),
wxYES_NO | wxCENTRE | wxRESIZE_BORDER | wxICON_QUESTION );
errorMsg.Trim();
dlg.SetExtendedMessage( errorMsg );
dlg.SetYesNoLabels( wxMessageDialog::ButtonLabel( _( "Continue with Rescue" ) ),
wxMessageDialog::ButtonLabel( _( "Abort Rescue" ) ) );
if( dlg.ShowModal() == wxID_NO )
return false;
}
return true;
}
void DIALOG_SYMBOL_REMAP::OnUpdateUIRemapButton( wxUpdateUIEvent& aEvent )
{
aEvent.Enable( !m_remapped );
}