Eeschema: fix edit sheet bug.

Remove an existing sheet from the current page that requires a schematic
load from file to prevent a corrupt screen list from causing a segfault.

Merge the edit sheet file loading with the append schematic code since
they are functionally the same.  This allows the sheet edit code to
take advantage of all of the added broken symbol library links added in
the recent append schematic fixes.

Fixes lp:1835841

https://bugs.launchpad.net/kicad/+bug/1835841
This commit is contained in:
Wayne Stambaugh 2019-07-29 15:59:31 -04:00
parent 6bf1ac45e3
commit b595dc0d3b
3 changed files with 560 additions and 508 deletions

View File

@ -402,14 +402,8 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector<wxString>& aFileSet, in
bool SCH_EDIT_FRAME::AppendSchematic() bool SCH_EDIT_FRAME::AppendSchematic()
{ {
int i;
wxString msg;
wxString fullFileName; wxString fullFileName;
wxString topLevelSheetPath;
wxFileName tmp;
SCH_SCREEN* screen = GetScreen(); SCH_SCREEN* screen = GetScreen();
bool libTableChanged = false;
if( !screen ) if( !screen )
{ {
@ -428,414 +422,10 @@ bool SCH_EDIT_FRAME::AppendSchematic()
fullFileName = dlg.GetPath(); fullFileName = dlg.GetPath();
wxFileName fn = fullFileName; if( !LoadSheetFromFile( GetCurrentSheet().Last(), &GetCurrentSheet(), fullFileName ) )
if( fn.IsRelative() )
{
fn.MakeAbsolute();
fullFileName = fn.GetFullPath();
}
// Load the schematic into a temporary sheet.
SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) );
std::unique_ptr< SCH_SHEET> newSheet( new SCH_SHEET );
newSheet->SetFileName( fullFileName );
try
{
pi->Load( fullFileName, &Kiway(), newSheet.get() );
if( !pi->GetError().IsEmpty() )
{
DisplayErrorMessage( this,
_( "The entire schematic could not be loaded. Errors "
"occurred attempting to load hierarchical sheet "
"schematics." ),
pi->GetError() );
}
}
catch( const IO_ERROR& ioe )
{
msg.Printf( _( "Error occurred loading schematic file \"%s\"." ), fullFileName );
DisplayErrorMessage( this, msg, ioe.What() );
msg.Printf( _( "Failed to load schematic \"%s\"" ), fullFileName );
AppendMsgPanel( wxEmptyString, msg, CYAN );
return false; return false;
}
tmp = fn;
// If the appended schematic is in a different folder from the current project and
// it contains hierarchical sheets, the hierarchical sheet paths need to be updated.
if( fn.GetPath( wxPATH_GET_SEPARATOR ) != Prj().GetProjectPath() && newSheet->CountSheets() )
{
// Give the user the option to choose relative path if possible.
if( tmp.MakeRelativeTo( Prj().GetProjectPath() ) )
{
wxMessageDialog msgDlg1(
this,
"Do you want to use a relative path to the appended "
"schematic?", "Select Path Type",
wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_QUESTION | wxCENTER );
msgDlg1.SetYesNoLabels( wxMessageDialog::ButtonLabel( "Use Relative Path" ),
wxMessageDialog::ButtonLabel( "Use Absolute Path" ) );
int rsp = msgDlg1.ShowModal();
if( rsp == wxID_CANCEL )
{
return false;
}
else if( rsp == wxID_NO )
{
topLevelSheetPath = fn.GetPathWithSep();
if( wxFileName::GetPathSeparator() == '\\' )
topLevelSheetPath.Replace( "\\", "/" );
}
else
{
topLevelSheetPath = tmp.GetPathWithSep( wxPATH_UNIX );
}
}
else
{
topLevelSheetPath = tmp.GetPathWithSep();
if( wxFileName::GetPathSeparator() == '\\' )
topLevelSheetPath.Replace( "\\", "/" );
}
}
// Make sure any new sheet changes do not cause any recursion issues.
SCH_SHEET_LIST hierarchy( g_RootSheet ); // This is the schematic sheet hierarchy.
SCH_SHEET_LIST sheetHierarchy( newSheet.get() ); // This is the hierarchy of the import.
wxFileName destFile = screen->GetFileName();
if( destFile.IsRelative() )
destFile.MakeAbsolute( Prj().GetProjectPath() );
if( hierarchy.TestForRecursion( sheetHierarchy, destFile.GetFullPath( wxPATH_UNIX ) ) )
{
msg.Printf( _( "The sheet changes cannot be made because the destination sheet already "
"has the sheet \"%s\" or one of it's subsheets as a parent somewhere in "
"the schematic hierarchy." ),
destFile.GetFullPath() );
DisplayError( this, msg );
return false;
}
wxArrayString names;
// Make sure the imported schematic has been remapped to the symbol library table.
SCH_SCREENS newScreens( newSheet.get() ); // All screens associated with the import.
if( newScreens.HasNoFullyDefinedLibIds() )
{
DisplayInfoMessage( this,
"This schematic has not been remapped to the symbol library\n"
"table. The project this schematic belongs to must first be\n"
"remapped before it can be imported into the current project." );
return false;
}
wxArrayString newLibNames;
SCH_SCREENS prjScreens( g_RootSheet );
newScreens.GetLibNicknames( names );
wxMessageDialog::ButtonLabel okButtonLabel( _( "Continue Append" ) );
wxMessageDialog::ButtonLabel cancelButtonLabel( _( "Cancel Append" ) );
if( fn.GetPath( wxPATH_GET_SEPARATOR ) == Prj().GetProjectPath()
&& !prjScreens.HasSchematic( fullFileName ) )
{
// A schematic in the current project path that isn't part of the current project.
// It's possible the user copied this schematic from another project so the library
// links may not be avaible. Even this is check is no guarantee that all symbol
// library links are valid but it's better than nothing.
for( const auto& name : names )
{
if( !Prj().SchSymbolLibTable()->HasLibrary( name ) )
newLibNames.Add( name );
}
if( !newLibNames.IsEmpty() )
{
msg = _( "There are library names in the appended schematic that are missing "
"from the project library table. This may result in broken symbol "
"library links for the appended schematic. Do you wish to continue?" );
wxMessageDialog msgDlg1( this, msg, _( "Continue Append Schematic" ),
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
wxCENTER | wxICON_QUESTION );
msgDlg1.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
if( msgDlg1.ShowModal() == wxID_CANCEL )
return false;
}
}
else if( fn.GetPath( wxPATH_GET_SEPARATOR ) != Prj().GetProjectPath() )
{
// A schematic loaded from a path other than the current project path.
// If there are symbol libraries in the imported schematic that are not in the
// symbol library table of this project, there could be a lot of broken symbol
// library links. Attempt to add the missing libraries to the project symbol
// library table.
wxArrayString duplicateLibNames;
for( const auto& name : names )
{
if( !Prj().SchSymbolLibTable()->HasLibrary( name ) )
newLibNames.Add( name );
else
duplicateLibNames.Add( name );
}
SYMBOL_LIB_TABLE table;
wxFileName symLibTableFn( fn.GetPath(), SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
// If there are any new or duplicate libraries, check to see if it's possible that
// there could be any missing libraries that would cause broken symbol library links.
if( !newLibNames.IsEmpty() || !duplicateLibNames.IsEmpty() )
{
if( !symLibTableFn.Exists() || !symLibTableFn.IsFileReadable() )
{
msg.Printf( _( "The project library table \"%s\" does not exist or cannot "
"be read. This may result in broken symbol links for the "
"appended schematic. Do you wish to continue?" ),
fn.GetFullPath() );
wxMessageDialog msgDlg2( this, msg, _( "Continue Append Schematic" ),
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
wxCENTER | wxICON_QUESTION );
msgDlg2.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
if( msgDlg2.ShowModal() == wxID_CANCEL )
return false;
}
else
{
try
{
table.Load( symLibTableFn.GetFullPath() );
}
catch( const IO_ERROR& ioe )
{
msg.Printf( _( "An error occurred loading the symbol library table \"%s\"." ),
symLibTableFn.GetFullPath() );
DisplayErrorMessage( NULL, msg, ioe.What() );
return false;
}
}
}
// Check to see if any of the symbol libraries found in the appended schematic do
// not exist in the current project are missing from the appended project symbol
// library table.
if( !newLibNames.IsEmpty() )
{
bool missingLibNames = table.IsEmpty();
if( !missingLibNames )
{
for( const auto& newLibName : newLibNames )
{
if( !table.HasLibrary( newLibName ) )
{
missingLibNames = true;
break;
}
}
}
if( missingLibNames )
{
msg = _( "There are library names in the appended schematic that are missing "
"from the appended schematic project library table. This may result "
"in broken symbol library links for the appended schematic. "
"Do you wish to continue?" );
wxMessageDialog msgDlg3( this, msg, _( "Continue Append Schematic" ),
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
wxCENTER | wxICON_QUESTION );
msgDlg3.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
if( msgDlg3.ShowModal() == wxID_CANCEL )
return false;
}
}
// The library name already exists in the current project. Check to see if the
// duplicate name is the same library in the current project. If it's not, it's
// most likely that the symbol library links will be broken.
if( !duplicateLibNames.IsEmpty() && !table.IsEmpty() )
{
bool libNameConflict = false;
for( const auto& duplicateLibName : duplicateLibNames )
{
const SYMBOL_LIB_TABLE_ROW* thisRow = nullptr;
const SYMBOL_LIB_TABLE_ROW* otherRow = nullptr;
if( Prj().SchSymbolLibTable()->HasLibrary( duplicateLibName ) )
thisRow = Prj().SchSymbolLibTable()->FindRow( duplicateLibName );
if( table.HasLibrary( duplicateLibName ) )
otherRow = table.FindRow( duplicateLibName );
// It's in the global library table so there is no conflict.
if( thisRow && !otherRow )
continue;
if( !thisRow || !otherRow )
continue;
wxFileName otherUriFileName;
wxString thisURI = thisRow->GetFullURI( true );
wxString otherURI = otherRow->GetFullURI( false);
if( otherURI.Contains( "${KIPRJMOD}" ) || otherURI.Contains( "$(KIPRJMOD)" ) )
{
// Cannot use relative paths here, "${KIPRJMOD}../path-to-cache-lib" does
// not expand to a valid symbol library path.
otherUriFileName.SetPath( fn.GetPath() );
otherUriFileName.SetFullName( otherURI.AfterLast( '}' ) );
otherURI = otherUriFileName.GetFullPath();
}
if( thisURI != otherURI )
{
libNameConflict = true;
break;
}
}
if( libNameConflict )
{
msg = _( "A duplicate library name that references a different library exists "
"in the current library table. This conflict cannot be resolved and "
"may result in broken symbol library links for the appended schematic. "
"Do you wish to continue?" );
wxMessageDialog msgDlg4( this, msg, _( "Continue Append Schematic" ),
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
wxCENTER | wxICON_QUESTION );
msgDlg4.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
if( msgDlg4.ShowModal() == wxID_CANCEL )
return false;
}
}
// All (most?) of the possible broken symbol library link cases are covered. Map the
// new appended schematic project symbol library table entries to the current project
// symbol library table.
if( !newLibNames.IsEmpty() && !table.IsEmpty() )
{
for( const auto& libName : newLibNames )
{
if( !table.HasLibrary( libName )
|| Prj().SchSymbolLibTable()->HasLibrary( libName ) )
continue;
// Don't expand environment variable because KIPRJMOD will not be correct
// for a different project.
wxString uri = table.GetFullURI( libName, false );
wxFileName newLib;
if( uri.Contains( "${KIPRJMOD}" ) || uri.Contains( "$(KIPRJMOD)" ) )
{
// Cannot use relative paths here, "${KIPRJMOD}../path-to-cache-lib" does
// not expand to a valid symbol library path.
newLib.SetPath( fn.GetPath() );
newLib.SetFullName( uri.AfterLast( '}' ) );
uri = newLib.GetFullPath();
}
else
{
uri = table.GetFullURI( libName );
}
// Add the library from the imported project to the current project
// symbol library table.
const SYMBOL_LIB_TABLE_ROW* row = table.FindRow( libName );
auto newRow = new SYMBOL_LIB_TABLE_ROW( libName, uri, row->GetType(),
row->GetOptions(), row->GetDescr() );
Prj().SchSymbolLibTable()->InsertRow( newRow );
libTableChanged = true;
}
}
}
newScreens.ClearAnnotation();
// Check for duplicate sheet names in the current page.
wxArrayString duplicateSheetNames;
EE_TYPE_COLLECTOR sheets;
sheets.Collect( screen->GetDrawItems(), EE_COLLECTOR::SheetsOnly );
for( i = 0; i < sheets.GetCount(); ++i )
{
if( newSheet->GetScreen()->GetSheet( ( ( SCH_SHEET* ) sheets[i] )->GetName() ) )
duplicateSheetNames.Add( ( ( SCH_SHEET* ) sheets[i] )->GetName() );
}
if( !duplicateSheetNames.IsEmpty() )
{
msg.Printf( "Duplicate sheet names exist on the current page. Do you want to "
"automatically rename the duplicate sheet names?" );
if( !IsOK( this, msg ) )
return false;
}
SCH_SCREEN* newScreen = newSheet->GetScreen();
wxCHECK_MSG( newScreen, false, "No screen defined for imported sheet." );
for( const auto& duplicateName : duplicateSheetNames )
{
SCH_SHEET* renamedSheet = newScreen->GetSheet( duplicateName );
wxCHECK2_MSG( renamedSheet, continue,
"Sheet " + duplicateName + " not found in imported schematic." );
timestamp_t newtimestamp = GetNewTimeStamp();
renamedSheet->SetTimeStamp( newtimestamp );
renamedSheet->SetName( wxString::Format( "Sheet%8.8lX", (unsigned long) newtimestamp ) );
}
EE_TYPE_COLLECTOR newTopLevelSheets;
newTopLevelSheets.Collect( newSheet->GetScreen()->GetDrawItems(), EE_COLLECTOR::SheetsOnly );
for( i = 0; i < newTopLevelSheets.GetCount(); ++i )
{
SCH_SHEET* tmpSheet = dynamic_cast< SCH_SHEET* >( newTopLevelSheets[i] );
wxCHECK2( tmpSheet != nullptr, continue );
tmpSheet->SetFileName( topLevelSheetPath + tmpSheet->GetFileName() );
}
if( libTableChanged )
Prj().SchSymbolLibTable()->Save( Prj().GetProjectPath() +
SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
// It is finally safe to add the imported schematic.
screen->Append( newScreen );
SCH_SCREENS allScreens;
allScreens.ReplaceDuplicateTimeStamps();
SCH_SCREENS screens( GetCurrentSheet().Last() ); SCH_SCREENS screens( GetCurrentSheet().Last() );
screens.UpdateSymbolLinks( true );
// Clear all annotation in the imported schematic to prevent clashes with existing annotation.
// Must be done after updating the symbol links as we need to know about multi-unit parts.
// screens.ClearAnnotation();
screens.TestDanglingEnds(); screens.TestDanglingEnds();
GetScreen()->SetGrid( ID_POPUP_GRID_LEVEL_1000 + m_LastGridSizeId ); GetScreen()->SetGrid( ID_POPUP_GRID_LEVEL_1000 + m_LastGridSizeId );

View File

@ -593,33 +593,9 @@ public:
bool OpenProjectFiles( const std::vector<wxString>& aFileSet, int aCtl = 0 ) override; bool OpenProjectFiles( const std::vector<wxString>& aFileSet, int aCtl = 0 ) override;
/** /**
* Import a KiCad schematic into the current page. * Import a KiCad schematic into the current sheet.
* *
* In order to import a schematic a lot of things have to happen to before the contents * @return True if the schematic was imported properly.
* of the imported schematic can be appended to the current page. The following list
* describes this process:
*
* - Load the schematic into a temporary SCH_SHEET object.
* - Make sure the imported schematic does not cause any hierarchy recursion issues.
* - Verify the imported schematic uses fully qualified #LIB_ID objects (symbol library table).
* - Check to see if any symbol libraries need to be added to the current project's symbol
* library table. This includes:
* - Check if the symbol library already exists in the project or global symbol library
* table.
* - Convert symbol library URLS that use the ${KIPRJMOD} environment variable to absolute
* paths. ${KIPRJMOD} will not be the same for this project.
* - Check for duplicate symbol library nicknames and change the new symbol library nickname
* to prevent library name clashes.
* - Update all schematic symbol LIB_ID object library nicknames when the library nickname
* was changed to prevent clashes.
* - Check for duplicate sheet names which is illegal and automatically rename any duplicate
* sheets in the imported schematic.
* - Clear all of the annotation in the imported schematic to prevent clashes.
* - Append the objects from the temporary sheet to the current page.
* - Replace any duplicate time stamps.
* - Refresh the symbol library links.
*
* @return True if the project was imported properly.
*/ */
bool AppendSchematic(); bool AppendSchematic();
@ -724,6 +700,34 @@ private:
*/ */
void NormalizeSchematicOnFirstLoad( bool recalculateConnections ); void NormalizeSchematicOnFirstLoad( bool recalculateConnections );
/**
* Verify that \a aSheet will not cause a recursion error in \a aHierarchy.
*
* @param aSheet is the #SCH_SHEET object to test.
* @param aHierarchy is the #SCH_SHEET_PATH where \a aSheet is going to reside.
*
* @return true if \a aSheet will cause a resursion error in \a aHierarchy.
*/
bool checkSheetForRecursion( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy );
/**
* Verify that the symbol library links \a aSheet and all of it's child sheets have
* been remapped to the symbol library table.
*
* @param aSheet is the #SCH_SHEET object to test.
*
* @return true if \a aSheet and it's child sheets have not been remapped.
*/
bool checkForNoFullyDefinedLibIds( SCH_SHEET* aSheet );
/**
* Load the given filename but sets the path to the current project path.
*
* @param full filepath of file to be imported.
* @param aFileType SCH_FILE_T value for file type
*/
bool importFile( const wxString& aFileName, int aFileType );
public: public:
/** /**
* Change a text type to another one. * Change a text type to another one.
@ -769,19 +773,53 @@ public:
void InitSheet( SCH_SHEET* aSheet, const wxString& aNewFilename ); void InitSheet( SCH_SHEET* aSheet, const wxString& aNewFilename );
void LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
const wxString& aExistingFilename );
private:
/** /**
* Load the given filename but sets the path to the current project path. * Load a the KiCad schematic file \a aFileName into the sheet \a aSheet.
* *
* @param full filepath of file to be imported. * If \a aSheet does not have a valid #SCH_SCREEN object, the schematic is loaded into
* @param aFileType SCH_FILE_T value for file type * \a Sheet. Otherwise, it is appended to the current #SCH_SCREEN object.
*
* In order to import a schematic a lot of things have to happen to before the contents
* of the imported schematic can be appended to the current page. The following list
* describes this process:
*
* - Load the schematic into a temporary #SCH_SHEET object.
* - Make sure the imported schematic does not cause any hierarchy recursion issues.
* - Verify the imported schematic uses fully qualified #LIB_ID objects (symbol library table).
* - Check all of the possible combinations that could cause broken symbol library links
* and give the user the option to cancel the append process. The following conditions
* are check but they still do not guarantee that there will not be any broken symbol
* library links:
* - The source schematic is in the current project path and contains symbol library
* nicknames not found in the project symbol library table. This can happen if the
* schematic is copied to the current project path from another project.
* - The source schematic is in a different path and there are symbol library link nicknames
* that do not exist in either the current symbol library table or the source project
* symbol library table if it exists in the source path.
* - The source schematic is in a different path and contains duplicate symbol library
* nicknames that point to different libraries.
* - Check to see if any symbol libraries need to be added to the current project's symbol
* library table. This includes:
* - Check if the symbol library already exists in the project or global symbol library
* table.
* - Convert symbol library URLS that use the ${KIPRJMOD} environment variable to absolute
* paths. ${KIPRJMOD} will not be the same for this project.
* - Clear all of the annotation in the imported schematic to prevent clashes.
* - Append the objects from the temporary sheet to the current page.
* - Replace any duplicate time stamps.
* - Refresh the symbol library links.
*
* @param aSheet is the sheet to either append or load the schematic.
* @param aHierarchy is the current position in the schematic hierarchy used to test for
* possible file recursion issues.
* @param aFileName is the file name to load. The file name is expected to have an absolute
* path.
*
* @return True if the schematic was imported properly.
*/ */
bool importFile( const wxString& aFileName, int aFileType ); bool LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
const wxString& aFileName );
public:
/** /**
* Create a new SCH_SHEET_PIN object and add it to \a aSheet at the current cursor position. * Create a new SCH_SHEET_PIN object and add it to \a aSheet at the current cursor position.
* *
@ -807,13 +845,11 @@ public:
int GetLabelIncrement() const { return m_repeatLabelDelta; } int GetLabelIncrement() const { return m_repeatLabelDelta; }
public:
void ConvertPart( SCH_COMPONENT* aComponent ); void ConvertPart( SCH_COMPONENT* aComponent );
void SelectUnit( SCH_COMPONENT* aComponent, int aUnit ); void SelectUnit( SCH_COMPONENT* aComponent, int aUnit );
/* Undo - redo */ /* Undo - redo */
public:
/** /**
* Create a copy of the current schematic item, and put it in the undo list. * Create a copy of the current schematic item, and put it in the undo list.

View File

@ -35,10 +35,61 @@
#include <sch_sheet.h> #include <sch_sheet.h>
#include <sch_sheet_path.h> #include <sch_sheet_path.h>
#include <sch_view.h> #include <sch_view.h>
#include <symbol_lib_table.h>
#include <dialogs/dialog_sch_sheet_props.h> #include <dialogs/dialog_sch_sheet_props.h>
#include <dialogs/dialog_sch_edit_sheet_pin.h> #include <dialogs/dialog_sch_edit_sheet_pin.h>
#include <tool/actions.h> #include <tool/actions.h>
bool SCH_EDIT_FRAME::checkSheetForRecursion( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy )
{
wxASSERT( aSheet && aHierarchy );
wxString msg;
SCH_SHEET_LIST hierarchy( g_RootSheet ); // This is the full schematic sheet hierarchy.
SCH_SHEET_LIST sheetHierarchy( aSheet ); // This is the hierarchy of the loaded file.
wxFileName destFile = aHierarchy->LastScreen()->GetFileName();
// SCH_SCREEN object file paths are expected to be absolute. If this assert fires,
// something is seriously broken.
wxASSERT( destFile.IsAbsolute() );
if( hierarchy.TestForRecursion( sheetHierarchy, destFile.GetFullPath( wxPATH_UNIX ) ) )
{
msg.Printf( _( "The sheet changes cannot be made because the destination sheet already "
"has the sheet \"%s\" or one of it's subsheets as a parent somewhere in "
"the schematic hierarchy." ),
destFile.GetFullPath() );
DisplayError( this, msg );
return true;
}
return false;
}
bool SCH_EDIT_FRAME::checkForNoFullyDefinedLibIds( SCH_SHEET* aSheet )
{
wxASSERT( aSheet && aSheet->GetScreen() );
wxString msg;
SCH_SCREENS newScreens( aSheet );
if( newScreens.HasNoFullyDefinedLibIds() )
{
msg.Printf( _( "The schematic \"%s\" has not had it's symbol library links remapped "
"to the symbol library table. The project this schematic belongs to "
"must first be remapped before it can be imported into the current "
"project." ), aSheet->GetScreen()->GetFileName() );
DisplayInfoMessage( this, msg );
return true;
}
return false;
}
void SCH_EDIT_FRAME::InitSheet( SCH_SHEET* aSheet, const wxString& aNewFilename ) void SCH_EDIT_FRAME::InitSheet( SCH_SHEET* aSheet, const wxString& aNewFilename )
{ {
aSheet->SetScreen( new SCH_SCREEN( &Kiway() ) ); aSheet->SetScreen( new SCH_SCREEN( &Kiway() ) );
@ -48,50 +99,422 @@ void SCH_EDIT_FRAME::InitSheet( SCH_SHEET* aSheet, const wxString& aNewFilename
} }
void SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
const wxString& aExistingFilename ) const wxString& aFileName )
{ {
SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) ); wxASSERT( aSheet && aHierarchy );
wxFileName fileName( aExistingFilename ); int i;
wxString msg;
wxString topLevelSheetPath;
wxFileName tmp;
wxFileName currentSheetFileName;
bool libTableChanged = false;
SCH_SCREEN* currentScreen = aHierarchy->LastScreen();
SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) );
std::unique_ptr< SCH_SHEET> newSheet( new SCH_SHEET );
wxFileName fileName( aFileName );
if( !fileName.IsAbsolute() ) if( !fileName.IsAbsolute() )
{ {
const SCH_SCREEN* currentScreen = aHierarchy->LastScreen(); wxCHECK_MSG( fileName.MakeAbsolute(), false,
wxFileName currentSheetFileName = currentScreen->GetFileName(); wxString::Format( "Cannot make file name \"%s\" path absolute.",
fileName.Normalize( wxPATH_NORM_ALL, currentSheetFileName.GetPath() ); aFileName ) );
} }
wxString fullFilename = fileName.GetFullPath(); wxString fullFilename = fileName.GetFullPath();
try try
{ {
pi->Load( fullFilename, &Kiway(), aSheet ); if( aSheet->GetScreen() != nullptr )
{
newSheet.reset( pi->Load( fullFilename, &Kiway() ) );
}
else
{
newSheet->SetFileName( fullFilename );
pi->Load( fullFilename, &Kiway(), newSheet.get() );
}
if( !pi->GetError().IsEmpty() ) if( !pi->GetError().IsEmpty() )
{ {
DisplayErrorMessage( this, _( "The entire schematic could not be loaded.\n" msg = _( "The entire schematic could not be loaded. Errors occurred attempting "
"Errors occurred loading hierarchical sheets." ), "to load hierarchical sheet schematics." );
pi->GetError() );
wxMessageDialog msgDlg1( this, msg, _( "Schematic Load Error" ),
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
wxCENTER | wxICON_QUESTION );
msgDlg1.SetOKLabel( wxMessageDialog::ButtonLabel( _( "Use partial schematic" ) ) );
msgDlg1.SetExtendedMessage( pi->GetError() );
if( msgDlg1.ShowModal() == wxID_CANCEL )
return false;
} }
} }
catch( const IO_ERROR& ioe ) catch( const IO_ERROR& ioe )
{ {
wxString msg;
msg.Printf( _( "Error occurred loading schematic file \"%s\"." ), fullFilename ); msg.Printf( _( "Error occurred loading schematic file \"%s\"." ), fullFilename );
DisplayErrorMessage( this, msg, ioe.What() ); DisplayErrorMessage( this, msg, ioe.What() );
msg.Printf( _( "Failed to load schematic \"%s\"" ), fullFilename ); msg.Printf( _( "Failed to load schematic \"%s\"" ), fullFilename );
AppendMsgPanel( wxEmptyString, msg, CYAN ); AppendMsgPanel( wxEmptyString, msg, CYAN );
return; return false;
} }
SCH_SCREEN* screen = aSheet->GetScreen(); tmp = fileName;
if( screen ) // If the loaded schematic is in a different folder from the current project and
screen->UpdateSymbolLinks( true ); // it contains hierarchical sheets, the hierarchical sheet paths need to be updated.
if( fileName.GetPath( wxPATH_GET_SEPARATOR ) != Prj().GetProjectPath()
&& newSheet->CountSheets() )
{
// Give the user the option to choose relative path if possible.
if( tmp.MakeRelativeTo( Prj().GetProjectPath() ) )
{
wxMessageDialog msgDlg2(
this,
"Do you want to use a relative path to the loaded "
"schematic?", "Select Path Type",
wxYES_NO | wxCANCEL | wxYES_DEFAULT | wxICON_QUESTION | wxCENTER );
msgDlg2.SetYesNoLabels( wxMessageDialog::ButtonLabel( "Use Relative Path" ),
wxMessageDialog::ButtonLabel( "Use Absolute Path" ) );
int rsp = msgDlg2.ShowModal();
if( rsp == wxID_CANCEL )
{
return false;
}
else if( rsp == wxID_NO )
{
topLevelSheetPath = fileName.GetPathWithSep();
if( wxFileName::GetPathSeparator() == '\\' )
topLevelSheetPath.Replace( "\\", "/" );
}
else
{
topLevelSheetPath = tmp.GetPathWithSep( wxPATH_UNIX );
}
}
else
{
topLevelSheetPath = tmp.GetPathWithSep();
if( wxFileName::GetPathSeparator() == '\\' )
topLevelSheetPath.Replace( "\\", "/" );
}
}
// Make sure any new sheet changes do not cause any recursion issues.
SCH_SHEET_LIST hierarchy( g_RootSheet ); // This is the schematic sheet hierarchy.
SCH_SHEET_LIST sheetHierarchy( newSheet.get() ); // This is the hierarchy of the loaded file.
if( checkSheetForRecursion( newSheet.get(), aHierarchy )
|| checkForNoFullyDefinedLibIds( newSheet.get() ) )
return false;
// Make a valiant attempt to warn the user of all possible scenarios where there could
// be broken symbol library links.
wxArrayString names;
wxArrayString newLibNames;
SCH_SCREENS newScreens( newSheet.get() ); // All screens associated with the import.
SCH_SCREENS prjScreens( g_RootSheet );
newScreens.GetLibNicknames( names );
wxMessageDialog::ButtonLabel okButtonLabel( _( "Continue Load" ) );
wxMessageDialog::ButtonLabel cancelButtonLabel( _( "Cancel Load" ) );
if( fileName.GetPath( wxPATH_GET_SEPARATOR ) == Prj().GetProjectPath()
&& !prjScreens.HasSchematic( fullFilename ) )
{
// A schematic in the current project path that isn't part of the current project.
// It's possible the user copied this schematic from another project so the library
// links may not be avaible. Even this is check is no guarantee that all symbol
// library links are valid but it's better than nothing.
for( const auto& name : names )
{
if( !Prj().SchSymbolLibTable()->HasLibrary( name ) )
newLibNames.Add( name );
}
if( !newLibNames.IsEmpty() )
{
msg = _( "There are library names in the loaded schematic that are missing "
"from the project library table. This may result in broken symbol "
"library links for the loaded schematic. Do you wish to continue?" );
wxMessageDialog msgDlg3( this, msg, _( "Continue Load Schematic" ),
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
wxCENTER | wxICON_QUESTION );
msgDlg3.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
if( msgDlg3.ShowModal() == wxID_CANCEL )
return false;
}
}
else if( fileName.GetPath( wxPATH_GET_SEPARATOR ) != Prj().GetProjectPath() )
{
// A schematic loaded from a path other than the current project path.
// If there are symbol libraries in the imported schematic that are not in the
// symbol library table of this project, there could be a lot of broken symbol
// library links. Attempt to add the missing libraries to the project symbol
// library table.
wxArrayString duplicateLibNames;
for( const auto& name : names )
{
if( !Prj().SchSymbolLibTable()->HasLibrary( name ) )
newLibNames.Add( name );
else
duplicateLibNames.Add( name );
}
SYMBOL_LIB_TABLE table;
wxFileName symLibTableFn( fileName.GetPath(),
SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
// If there are any new or duplicate libraries, check to see if it's possible that
// there could be any missing libraries that would cause broken symbol library links.
if( !newLibNames.IsEmpty() || !duplicateLibNames.IsEmpty() )
{
if( !symLibTableFn.Exists() || !symLibTableFn.IsFileReadable() )
{
msg.Printf( _( "The project library table \"%s\" does not exist or cannot "
"be read. This may result in broken symbol links for the "
"schematic. Do you wish to continue?" ),
fileName.GetFullPath() );
wxMessageDialog msgDlg4( this, msg, _( "Continue Load Schematic" ),
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
wxCENTER | wxICON_QUESTION );
msgDlg4.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
if( msgDlg4.ShowModal() == wxID_CANCEL )
return false;
}
else
{
try
{
table.Load( symLibTableFn.GetFullPath() );
}
catch( const IO_ERROR& ioe )
{
msg.Printf( _( "An error occurred loading the symbol library table "
"\"%s\"." ),
symLibTableFn.GetFullPath() );
DisplayErrorMessage( NULL, msg, ioe.What() );
return false;
}
}
}
// Check to see if any of the symbol libraries found in the appended schematic do
// not exist in the current project are missing from the appended project symbol
// library table.
if( !newLibNames.IsEmpty() )
{
bool missingLibNames = table.IsEmpty();
if( !missingLibNames )
{
for( const auto& newLibName : newLibNames )
{
if( !table.HasLibrary( newLibName ) )
{
missingLibNames = true;
break;
}
}
}
if( missingLibNames )
{
msg = _( "There are library names in the loaded schematic that are missing "
"from the loaded schematic project library table. This may result "
"in broken symbol library links for the schematic. "
"Do you wish to continue?" );
wxMessageDialog msgDlg5( this, msg, _( "Continue Load Schematic" ),
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
wxCENTER | wxICON_QUESTION );
msgDlg5.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
if( msgDlg5.ShowModal() == wxID_CANCEL )
return false;
}
}
// The library name already exists in the current project. Check to see if the
// duplicate name is the same library in the current project. If it's not, it's
// most likely that the symbol library links will be broken.
if( !duplicateLibNames.IsEmpty() && !table.IsEmpty() )
{
bool libNameConflict = false;
for( const auto& duplicateLibName : duplicateLibNames )
{
const SYMBOL_LIB_TABLE_ROW* thisRow = nullptr;
const SYMBOL_LIB_TABLE_ROW* otherRow = nullptr;
if( Prj().SchSymbolLibTable()->HasLibrary( duplicateLibName ) )
thisRow = Prj().SchSymbolLibTable()->FindRow( duplicateLibName );
if( table.HasLibrary( duplicateLibName ) )
otherRow = table.FindRow( duplicateLibName );
// It's in the global library table so there is no conflict.
if( thisRow && !otherRow )
continue;
if( !thisRow || !otherRow )
continue;
wxFileName otherUriFileName;
wxString thisURI = thisRow->GetFullURI( true );
wxString otherURI = otherRow->GetFullURI( false);
if( otherURI.Contains( "${KIPRJMOD}" ) || otherURI.Contains( "$(KIPRJMOD)" ) )
{
// Cannot use relative paths here, "${KIPRJMOD}../path-to-cache-lib" does
// not expand to a valid symbol library path.
otherUriFileName.SetPath( fileName.GetPath() );
otherUriFileName.SetFullName( otherURI.AfterLast( '}' ) );
otherURI = otherUriFileName.GetFullPath();
}
if( thisURI != otherURI )
{
libNameConflict = true;
break;
}
}
if( libNameConflict )
{
msg = _( "A duplicate library name that references a different library exists "
"in the current library table. This conflict cannot be resolved and "
"may result in broken symbol library links for the schematic. "
"Do you wish to continue?" );
wxMessageDialog msgDlg6( this, msg, _( "Continue Load Schematic" ),
wxOK | wxCANCEL | wxCANCEL_DEFAULT |
wxCENTER | wxICON_QUESTION );
msgDlg6.SetOKCancelLabels( okButtonLabel, cancelButtonLabel );
if( msgDlg6.ShowModal() == wxID_CANCEL )
return false;
}
}
// All (most?) of the possible broken symbol library link cases are covered. Map the
// new appended schematic project symbol library table entries to the current project
// symbol library table.
if( !newLibNames.IsEmpty() && !table.IsEmpty() )
{
for( const auto& libName : newLibNames )
{
if( !table.HasLibrary( libName )
|| Prj().SchSymbolLibTable()->HasLibrary( libName ) )
continue;
// Don't expand environment variable because KIPRJMOD will not be correct
// for a different project.
wxString uri = table.GetFullURI( libName, false );
wxFileName newLib;
if( uri.Contains( "${KIPRJMOD}" ) || uri.Contains( "$(KIPRJMOD)" ) )
{
// Cannot use relative paths here, "${KIPRJMOD}../path-to-cache-lib" does
// not expand to a valid symbol library path.
newLib.SetPath( fileName.GetPath() );
newLib.SetFullName( uri.AfterLast( '}' ) );
uri = newLib.GetFullPath();
}
else
{
uri = table.GetFullURI( libName );
}
// Add the library from the imported project to the current project
// symbol library table.
const SYMBOL_LIB_TABLE_ROW* row = table.FindRow( libName );
auto newRow = new SYMBOL_LIB_TABLE_ROW( libName, uri, row->GetType(),
row->GetOptions(), row->GetDescr() );
Prj().SchSymbolLibTable()->InsertRow( newRow );
libTableChanged = true;
}
}
}
// Check for duplicate sheet names in the current page.
wxArrayString duplicateSheetNames;
EE_TYPE_COLLECTOR sheets;
sheets.Collect( currentScreen->GetDrawItems(), EE_COLLECTOR::SheetsOnly );
for( i = 0; i < sheets.GetCount(); ++i )
{
if( newSheet->GetScreen()->GetSheet( ( ( SCH_SHEET* ) sheets[i] )->GetName() ) )
duplicateSheetNames.Add( ( ( SCH_SHEET* ) sheets[i] )->GetName() );
}
if( !duplicateSheetNames.IsEmpty() )
{
msg.Printf( "Duplicate sheet names exist on the current page. Do you want to "
"automatically rename the duplicate sheet names?" );
if( !IsOK( this, msg ) )
return false;
}
// Rename all duplicate sheet names.
SCH_SCREEN* newScreen = newSheet->GetScreen();
wxCHECK_MSG( newScreen, false, "No screen defined for sheet." );
for( const auto& duplicateName : duplicateSheetNames )
{
SCH_SHEET* renamedSheet = newScreen->GetSheet( duplicateName );
wxCHECK2_MSG( renamedSheet, continue,
"Sheet " + duplicateName + " not found in imported schematic." );
timestamp_t newtimestamp = GetNewTimeStamp();
renamedSheet->SetTimeStamp( newtimestamp );
renamedSheet->SetName( wxString::Format( "Sheet%8.8lX", (unsigned long) newtimestamp ) );
}
// Set all sheets loaded into the correct sheet file paths.
EE_TYPE_COLLECTOR newTopLevelSheets;
newTopLevelSheets.Collect( newSheet->GetScreen()->GetDrawItems(), EE_COLLECTOR::SheetsOnly );
for( i = 0; i < newTopLevelSheets.GetCount(); ++i )
{
SCH_SHEET* tmpSheet = dynamic_cast< SCH_SHEET* >( newTopLevelSheets[i] );
wxCHECK2( tmpSheet != nullptr, continue );
tmpSheet->SetFileName( topLevelSheetPath + tmpSheet->GetFileName() );
}
if( libTableChanged )
Prj().SchSymbolLibTable()->Save( Prj().GetProjectPath() +
SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
// It is finally safe to add or append the imported schematic.
if( aSheet->GetScreen() == nullptr )
aSheet->SetScreen( newScreen );
else
aSheet->GetScreen()->Append( newScreen );
SCH_SCREENS allScreens;
allScreens.ReplaceDuplicateTimeStamps();
SCH_SCREENS screens( aSheet );
screens.UpdateSymbolLinks( true );
return true;
} }
@ -101,8 +524,6 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
if( aSheet == NULL || aHierarchy == NULL ) if( aSheet == NULL || aHierarchy == NULL )
return false; return false;
SCH_SHEET_LIST hierarchy( g_RootSheet ); // This is the schematic sheet hierarchy.
// Get the new texts // Get the new texts
DIALOG_SCH_SHEET_PROPS dlg( this, aSheet ); DIALOG_SCH_SHEET_PROPS dlg( this, aSheet );
@ -113,8 +534,11 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
fileName.SetExt( SchematicFileExtension ); fileName.SetExt( SchematicFileExtension );
wxString msg; wxString msg;
bool renameFile = false;
bool loadFromFile = false; bool loadFromFile = false;
bool clearAnnotation = false; bool clearAnnotation = false;
bool restoreSheet = false;
bool isExistingSheet = false;
SCH_SCREEN* useScreen = NULL; SCH_SCREEN* useScreen = NULL;
// Relative file names are relative to the path of the current sheet. This allows for // Relative file names are relative to the path of the current sheet. This allows for
@ -146,9 +570,9 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
// Inside Eeschema, filenames are stored using unix notation // Inside Eeschema, filenames are stored using unix notation
newFilename.Replace( wxT( "\\" ), wxT( "/" ) ); newFilename.Replace( wxT( "\\" ), wxT( "/" ) );
if( aSheet->GetScreen() == NULL ) // New sheet. if( aSheet->GetScreen() == NULL ) // New sheet.
{ {
if( useScreen || loadFromFile ) // Load from existing file. if( useScreen || loadFromFile ) // Load from existing file.
{ {
clearAnnotation = true; clearAnnotation = true;
@ -162,19 +586,20 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
return false; return false;
} }
else // New file. else // New file.
{ {
InitSheet( aSheet, newFilename ); InitSheet( aSheet, newFilename );
} }
} }
else // Existing sheet. else // Existing sheet.
{ {
bool isUndoable = true; bool isUndoable = true;
bool renameFile = false;
wxString replaceMsg; wxString replaceMsg;
wxString newMsg; wxString newMsg;
wxString noUndoMsg; wxString noUndoMsg;
isExistingSheet = true;
// Changing the filename of a sheet can modify the full hierarchy structure // Changing the filename of a sheet can modify the full hierarchy structure
// and can be not always undoable. // and can be not always undoable.
// So prepare messages for user notifications: // So prepare messages for user notifications:
@ -202,7 +627,7 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
// Sheet file name changes cannot be undone. // Sheet file name changes cannot be undone.
isUndoable = false; isUndoable = false;
if( useScreen || loadFromFile ) // Load from existing file. if( useScreen || loadFromFile ) // Load from existing file.
{ {
clearAnnotation = true; clearAnnotation = true;
@ -214,7 +639,7 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
if( loadFromFile ) if( loadFromFile )
aSheet->SetScreen( NULL ); aSheet->SetScreen( NULL );
} }
else // Save to new file name. else // Save to new file name.
{ {
if( aSheet->GetScreenCount() > 1 ) if( aSheet->GetScreenCount() > 1 )
{ {
@ -270,17 +695,50 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
wxFileName userFileName = dlg.GetFileName(); wxFileName userFileName = dlg.GetFileName();
userFileName.SetExt( SchematicFileExtension ); userFileName.SetExt( SchematicFileExtension );
aSheet->SetFileName( userFileName.GetFullPath( wxPATH_UNIX ) );
if( useScreen ) if( useScreen )
{ {
// Create a temporary sheet for recursion testing to prevent a possible recursion error.
std::unique_ptr< SCH_SHEET> tmpSheet( new SCH_SHEET );
tmpSheet->SetName( dlg.GetSheetName() );
tmpSheet->SetFileName( userFileName.GetFullPath( wxPATH_UNIX ) );
tmpSheet->SetScreen( useScreen );
// No need to check for valid library IDs if we are using an existing screen.
if( checkSheetForRecursion( tmpSheet.get(), aHierarchy ) )
{
if( restoreSheet )
aHierarchy->LastScreen()->Append( aSheet );
return false;
}
// It's safe to set the sheet screen now.
aSheet->SetScreen( useScreen ); aSheet->SetScreen( useScreen );
} }
else if( loadFromFile ) else if( loadFromFile )
{ {
LoadSheetFromFile( aSheet, aHierarchy, newFilename ); if( isExistingSheet )
{
// Temporarily remove the sheet from the current schematic page so that recursion
// and symbol library link tests can be performed with the modified sheet settings.
restoreSheet = true;
aHierarchy->LastScreen()->Remove( aSheet );
}
if( !LoadSheetFromFile( aSheet, aHierarchy, newFilename ) )
{
if( restoreSheet )
aHierarchy->LastScreen()->Append( aSheet );
return false;
}
if( restoreSheet )
aHierarchy->LastScreen()->Append( aSheet );
} }
aSheet->SetFileName( userFileName.GetFullPath( wxPATH_UNIX ) );
aSheet->SetFileNameSize( dlg.GetFileNameTextSize() ); aSheet->SetFileNameSize( dlg.GetFileNameTextSize() );
aSheet->SetName( dlg.GetSheetName() ); aSheet->SetName( dlg.GetSheetName() );
aSheet->SetSheetNameSize( dlg.GetSheetNameTextSize() ); aSheet->SetSheetNameSize( dlg.GetSheetNameTextSize() );
@ -289,38 +747,6 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy,
aSheet->SetName( wxString::Format( wxT( "Sheet%8.8lX" ), aSheet->SetName( wxString::Format( wxT( "Sheet%8.8lX" ),
(long unsigned) aSheet->GetTimeStamp() ) ); (long unsigned) aSheet->GetTimeStamp() ) );
// Make sure the sheet changes do not cause any recursion.
SCH_SHEET_LIST sheetHierarchy( aSheet );
// Make sure files have fully qualified path and file name.
wxFileName destFn = aHierarchy->Last()->GetFileName();
if( destFn.IsRelative() )
destFn.MakeAbsolute( Prj().GetProjectPath() );
if( hierarchy.TestForRecursion( sheetHierarchy, destFn.GetFullPath( wxPATH_UNIX ) ) )
{
msg.Printf( _( "The sheet changes cannot be made because the destination sheet already "
"has the sheet \"%s\" or one of it's subsheets as a parent somewhere in "
"the schematic hierarchy." ),
newFilename );
DisplayError( this, msg );
return false;
}
// Check to make sure the symbols have been remapped to the symbol library table.
SCH_SCREENS newScreens( aSheet );
if( newScreens.HasNoFullyDefinedLibIds() )
{
msg.Printf(_( "The schematic \"%s\" has not been remapped to the symbol\nlibrary table. "
" The project this schematic belongs to must first be remapped\nbefore it "
"can be imported into the current project." ), fileName.GetFullName() );
DisplayInfoMessage( this, msg );
return false;
}
if( aClearAnnotationNewItems ) if( aClearAnnotationNewItems )
*aClearAnnotationNewItems = clearAnnotation; *aClearAnnotationNewItems = clearAnnotation;