/** * @file dialog_symbol_remap.cpp */ /* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2017 Wayne Stambaugh * 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 . */ #include #include #include #include #include #include #include #include #include "widgets/wx_html_report_panel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include 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; auto viewer = (SYMBOL_VIEWER_FRAME*) parent->Kiway().Player( FRAME_SCH_VIEWER, false ); if( viewer ) viewer->ReCreateLibList(); 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() ); 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. 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 libs; if( getLibsNotInGlobalSymbolLibTable( libs ) ) { wxBusyCursor busy; SYMBOL_LIB_TABLE libTable; std::vector 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. libName.Replace( wxS( " " ), wxS( "-" ) ); // Don't create duplicate table entries. 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 ); } 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() ) { for( EDA_ITEM* item : screen->Items().OfType( SCH_SYMBOL_T ) ) { symbol = dynamic_cast( item ); 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 ); schematic.UpdateSymbolLinks(); } bool DIALOG_SYMBOL_REMAP::remapSymbolToLibTable( SCH_SYMBOL* aSymbol ) { 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. 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() ); srcFileName.SetName( Prj().GetProjectName() + wxS( "-cache" ) ); srcFileName.SetExt( 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. 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( 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 ); }