diff --git a/eeschema/files-io.cpp b/eeschema/files-io.cpp index 2289d7ce01..9c7821c40f 100644 --- a/eeschema/files-io.cpp +++ b/eeschema/files-io.cpp @@ -386,14 +386,8 @@ bool SCH_EDIT_FRAME::OpenProjectFiles( const std::vector& aFileSet, in bool SCH_EDIT_FRAME::AppendSchematic() { - int i; - wxString msg; wxString fullFileName; - wxString topLevelSheetPath; - wxFileName tmp; SCH_SCREEN* screen = GetScreen(); - bool libTableChanged = false; - if( !screen ) { @@ -412,414 +406,10 @@ bool SCH_EDIT_FRAME::AppendSchematic() fullFileName = dlg.GetPath(); - wxFileName fn = 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 ); - + if( !LoadSheetFromFile( GetCurrentSheet().Last(), &GetCurrentSheet(), fullFileName ) ) 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; - SCH_TYPE_COLLECTOR sheets; - - sheets.Collect( screen->GetDrawItems(), SCH_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 ) ); - } - - SCH_TYPE_COLLECTOR newTopLevelSheets; - - newTopLevelSheets.Collect( newSheet->GetScreen()->GetDrawItems(), SCH_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() ); - 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(); GetScreen()->SetGrid( ID_POPUP_GRID_LEVEL_1000 + m_LastGridSizeId ); diff --git a/eeschema/sch_edit_frame.h b/eeschema/sch_edit_frame.h index 20154c8807..51799e3796 100644 --- a/eeschema/sch_edit_frame.h +++ b/eeschema/sch_edit_frame.h @@ -703,33 +703,9 @@ public: bool OpenProjectFiles( const std::vector& 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 - * 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. + * @return True if the schematic was imported properly. */ bool AppendSchematic(); @@ -980,6 +956,35 @@ private: void EditSchematicText( SCH_TEXT* TextStruct ); void ChangeTextOrient( SCH_TEXT* aTextItem ); + /** + * 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: /** * Command event handler to change a text type to another one. * @@ -1102,15 +1107,54 @@ public: wxPoint GetLastSheetPinPosition() const { return m_lastSheetPinPosition; } private: - /** - * 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 ); + void InitSheet( SCH_SHEET* aSheet, const wxString& aNewFilename ); - bool validateSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy ); + /** + * Load a the KiCad schematic file \a aFileName into the sheet \a aSheet. + * + * If \a aSheet does not have a valid #SCH_SCREEN object, the schematic is loaded into + * \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 LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, + const wxString& aFileName ); /** * Create a new SCH_SHEET_PIN object and add it to \a aSheet at the current cursor position. @@ -1210,7 +1254,6 @@ public: */ void OrientComponent( COMPONENT_ORIENTATION_T aOrientation = CMP_NORMAL ); -private: void OnSelectUnit( wxCommandEvent& aEvent ); void ConvertPart( SCH_COMPONENT* DrawComponent ); @@ -1229,7 +1272,6 @@ private: void PasteListOfItems( wxDC* DC ); /* Undo - redo */ -public: /** * Create a copy of the current schematic item, and put it in the undo list. diff --git a/eeschema/sheet.cpp b/eeschema/sheet.cpp index 8681fd523d..12a6d5589b 100644 --- a/eeschema/sheet.cpp +++ b/eeschema/sheet.cpp @@ -38,8 +38,487 @@ #include #include #include - +#include #include +#include +#include + + +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 ) +{ + aSheet->SetScreen( new SCH_SCREEN( &Kiway() ) ); + aSheet->GetScreen()->SetModify(); + aSheet->GetScreen()->SetMaxUndoItems( m_UndoRedoCountMax ); + aSheet->GetScreen()->SetFileName( aNewFilename ); +} + + +bool SCH_EDIT_FRAME::LoadSheetFromFile( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, + const wxString& aFileName ) +{ + wxASSERT( aSheet && aHierarchy ); + + 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() ) + { + wxCHECK_MSG( fileName.MakeAbsolute(), false, + wxString::Format( "Cannot make file name \"%s\" path absolute.", + aFileName ) ); + } + + wxString fullFilename = fileName.GetFullPath(); + + try + { + if( aSheet->GetScreen() != nullptr ) + { + newSheet.reset( pi->Load( fullFilename, &Kiway() ) ); + } + else + { + newSheet->SetFileName( fullFilename ); + pi->Load( fullFilename, &Kiway(), newSheet.get() ); + } + + if( !pi->GetError().IsEmpty() ) + { + msg = _( "The entire schematic could not be loaded. Errors occurred attempting " + "to load hierarchical sheet schematics." ); + + 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 ) + { + 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; + } + + tmp = fileName; + + // If the loaded schematic is in a different folder from the current project and + // 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; + SCH_TYPE_COLLECTOR sheets; + + sheets.Collect( currentScreen->GetDrawItems(), SCH_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. + SCH_TYPE_COLLECTOR newTopLevelSheets; + + newTopLevelSheets.Collect( newSheet->GetScreen()->GetDrawItems(), SCH_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; +} bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, @@ -48,8 +527,6 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, if( aSheet == NULL || aHierarchy == NULL ) return false; - SCH_SHEET_LIST hierarchy( g_RootSheet ); // This is the schematic sheet hierarchy. - // Get the new texts DIALOG_SCH_SHEET_PROPS dlg( this, aSheet ); @@ -60,8 +537,11 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, fileName.SetExt( SchematicFileExtension ); wxString msg; + bool renameFile = false; bool loadFromFile = false; bool clearAnnotation = false; + bool restoreSheet = false; + bool isExistingSheet = false; SCH_SCREEN* useScreen = NULL; // Relative file names are relative to the path of the current sheet. This allows for @@ -95,9 +575,9 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, SCH_PLUGIN::SCH_PLUGIN_RELEASER pi( SCH_IO_MGR::FindPlugin( SCH_IO_MGR::SCH_LEGACY ) ); - 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; @@ -111,7 +591,7 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, return false; } - else // New file. + else // New file. { aSheet->SetScreen( new SCH_SCREEN( &Kiway() ) ); aSheet->GetScreen()->SetModify(); @@ -119,14 +599,15 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, aSheet->GetScreen()->SetFileName( newFilename ); } } - else // Existing sheet. + else // Existing sheet. { bool isUndoable = true; - bool renameFile = false; wxString replaceMsg; wxString newMsg; wxString noUndoMsg; + isExistingSheet = true; + // Changing the filename of a sheet can modify the full hierarchy structure // and can be not always undoable. // So prepare messages for user notifications: @@ -154,7 +635,7 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, // Sheet file name changes cannot be undone. isUndoable = false; - if( useScreen || loadFromFile ) // Load from existing file. + if( useScreen || loadFromFile ) // Load from existing file. { clearAnnotation = true; @@ -166,7 +647,7 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, if( loadFromFile ) aSheet->SetScreen( NULL ); } - else // Save to new file name. + else // Save to new file name. { if( aSheet->GetScreenCount() > 1 ) { @@ -222,43 +703,50 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, wxFileName userFileName = dlg.GetFileName(); userFileName.SetExt( SchematicFileExtension ); - aSheet->SetFileName( userFileName.GetFullPath( wxPATH_UNIX ) ); if( useScreen ) { - aSheet->SetScreen( useScreen ); - } - else if( loadFromFile ) - { - try - { - aSheet = pi->Load( newFilename, &Kiway(), aSheet ); + // 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 ); - if( !pi->GetError().IsEmpty() ) - { - DisplayErrorMessage( this, - _( "The entire schematic could not be loaded.\n" - "Errors occurred loading hierarchical sheets." ), - pi->GetError() ); - } - } - catch( const IO_ERROR& ioe ) + // No need to check for valid library IDs if we are using an existing screen. + if( checkSheetForRecursion( tmpSheet.get(), aHierarchy ) ) { - msg.Printf( _( "Error occurred loading schematic file \"%s\"." ), newFilename ); - DisplayErrorMessage( this, msg, ioe.What() ); - - msg.Printf( _( "Failed to load schematic \"%s\"" ), newFilename ); - AppendMsgPanel( wxEmptyString, msg, CYAN ); + if( restoreSheet ) + aHierarchy->LastScreen()->Append( aSheet ); return false; } - SCH_SCREEN* screen = aSheet->GetScreen(); + // It's safe to set the sheet screen now. + aSheet->SetScreen( useScreen ); + } + else if( loadFromFile ) + { + 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( screen ) - screen->UpdateSymbolLinks( true ); + 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->SetName( dlg.GetSheetName() ); aSheet->SetSheetNameSize( dlg.GetSheetNameTextSize() ); @@ -267,38 +755,6 @@ bool SCH_EDIT_FRAME::EditSheet( SCH_SHEET* aSheet, SCH_SHEET_PATH* aHierarchy, aSheet->SetName( wxString::Format( wxT( "Sheet%8.8lX" ), (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 ) *aClearAnnotationNewItems = clearAnnotation;