You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

503 lines
18 KiB

  1. /**
  2. * @file dialog_symbol_remap.cpp
  3. */
  4. /*
  5. * This program source code file is part of KiCad, a free EDA CAD application.
  6. *
  7. * Copyright (C) 2017 Wayne Stambaugh <stambaughw@verizon.net>
  8. * Copyright (C) 2017-2019 KiCad Developers, see AUTHORS.txt for contributors.
  9. *
  10. * This program is free software: you can redistribute it and/or modify it
  11. * under the terms of the GNU General Public License as published by the
  12. * Free Software Foundation, either version 3 of the License, or (at your
  13. * option) any later version.
  14. *
  15. * This program is distributed in the hope that it will be useful, but
  16. * WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  18. * General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU General Public License along
  21. * with this program. If not, see <http://www.gnu.org/licenses/>.
  22. */
  23. #include <macros.h>
  24. #include <pgm_base.h>
  25. #include <kiface_i.h>
  26. #include <project.h>
  27. #include <confirm.h>
  28. #include <reporter.h>
  29. #include <wildcards_and_files_ext.h>
  30. #include <wx_html_report_panel.h>
  31. #include <class_library.h>
  32. #include <lib_view_frame.h>
  33. #include <project_rescue.h>
  34. #include <sch_io_mgr.h>
  35. #include <sch_sheet.h>
  36. #include <sch_component.h>
  37. #include <sch_screen.h>
  38. #include <sch_edit_frame.h>
  39. #include <symbol_lib_table.h>
  40. #include <env_paths.h>
  41. #include <dialog_symbol_remap.h>
  42. DIALOG_SYMBOL_REMAP::DIALOG_SYMBOL_REMAP( SCH_EDIT_FRAME* aParent ) :
  43. DIALOG_SYMBOL_REMAP_BASE( aParent )
  44. {
  45. m_remapped = false;
  46. if( !wxFileName::IsDirWritable( Prj().GetProjectPath() ) )
  47. {
  48. DisplayInfoMessage( this, _( "Remapping is not possible because you do not have "
  49. "write privileges to the project folder \"%s\"." ) );
  50. // Disable the remap button.
  51. m_remapped = true;
  52. }
  53. wxString text;
  54. text = _( "This schematic currently uses the project symbol library list look up method "
  55. "for loading library symbols. KiCad will attempt to map the existing symbols "
  56. "to use the new symbol library table. Remapping will change some project files "
  57. "and schematics may not be compatible with older versions of KiCad. All files "
  58. "that are changed will be backed up to the \"remap_backup\" folder in the project "
  59. "folder should you need to revert any changes. If you choose to skip this step, "
  60. "you will be responsible for manually remapping the symbols." );
  61. m_htmlCtrl->AppendToPage( text );
  62. }
  63. void DIALOG_SYMBOL_REMAP::OnRemapSymbols( wxCommandEvent& aEvent )
  64. {
  65. SCH_EDIT_FRAME* parent = dynamic_cast< SCH_EDIT_FRAME* >( GetParent() );
  66. wxCHECK_RET( parent != nullptr, "Parent window is not type SCH_EDIT_FRAME." );
  67. wxBusyCursor busy;
  68. if( !backupProject( m_messagePanel->Reporter() ) )
  69. return;
  70. // Ignore the never show rescue setting for one last rescue of legacy symbol
  71. // libraries before remapping to the symbol library table. This ensures the
  72. // best remapping results.
  73. LEGACY_RESCUER rescuer( Prj(), &parent->GetCurrentSheet(), parent->GetCanvas()->GetBackend() );
  74. if( RESCUER::RescueProject( this, rescuer, false ) )
  75. {
  76. LIB_VIEW_FRAME* viewer = (LIB_VIEW_FRAME*) parent->Kiway().Player( FRAME_SCH_VIEWER, false );
  77. if( viewer )
  78. viewer->ReCreateListLib();
  79. parent->GetScreen()->ClearUndoORRedoList( parent->GetScreen()->m_UndoList, 1 );
  80. parent->SyncView();
  81. parent->GetCanvas()->Refresh();
  82. parent->OnModify();
  83. }
  84. // The schematic is fully loaded, any legacy library symbols have been rescued. Now
  85. // check to see if the schematic has not been converted to the symbol library table
  86. // method for looking up symbols.
  87. wxFileName prjSymLibTableFileName( Prj().GetProjectPath(),
  88. SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
  89. // Delete the existing project symbol library table.
  90. if( prjSymLibTableFileName.FileExists() )
  91. {
  92. wxRemoveFile( prjSymLibTableFileName.GetFullPath() );
  93. }
  94. createProjectSymbolLibTable( m_messagePanel->Reporter() );
  95. Prj().SetElem( PROJECT::ELEM_SYMBOL_LIB_TABLE, NULL );
  96. Prj().SchSymbolLibTable();
  97. remapSymbolsToLibTable( m_messagePanel->Reporter() );
  98. // Remove all of the libraries from the legacy library list.
  99. wxString paths;
  100. wxArrayString libNames;
  101. PART_LIBS::LibNamesAndPaths( &Prj(), true, &paths, &libNames );
  102. // Reload the the cache symbol library.
  103. Prj().SetElem( PROJECT::ELEM_SCH_PART_LIBS, NULL );
  104. Prj().SchLibs();
  105. Raise();
  106. m_remapped = true;
  107. }
  108. size_t DIALOG_SYMBOL_REMAP::getLibsNotInGlobalSymbolLibTable( std::vector< PART_LIB* >& aLibs )
  109. {
  110. PART_LIBS* libs = Prj().SchLibs();
  111. for( PART_LIBS_BASE::iterator it = libs->begin(); it != libs->end(); ++it )
  112. {
  113. // Ignore the cache library.
  114. if( it->IsCache() )
  115. continue;
  116. // Check for the obvious library name.
  117. wxString libFileName = it->GetFullFileName();
  118. if( !SYMBOL_LIB_TABLE::GetGlobalLibTable().FindRowByURI( libFileName ) )
  119. aLibs.push_back( &(*it) );
  120. }
  121. return aLibs.size();
  122. }
  123. void DIALOG_SYMBOL_REMAP::createProjectSymbolLibTable( REPORTER& aReporter )
  124. {
  125. wxString msg;
  126. std::vector< PART_LIB* > libs;
  127. if( getLibsNotInGlobalSymbolLibTable( libs ) )
  128. {
  129. SYMBOL_LIB_TABLE prjLibTable;
  130. std::vector< wxString > libNames = SYMBOL_LIB_TABLE::GetGlobalLibTable().GetLogicalLibs();
  131. for( auto lib : libs )
  132. {
  133. wxString libName = lib->GetName();
  134. int libNameInc = 1;
  135. int libNameLen = libName.Length();
  136. // Spaces in the file name will break the symbol name because they are not
  137. // quoted in the symbol library file format.
  138. libName.Replace( " ", "-" );
  139. // Don't create duplicate table entries.
  140. while( std::find( libNames.begin(), libNames.end(), libName ) != libNames.end() )
  141. {
  142. libName = libName.Left( libNameLen );
  143. libName << libNameInc;
  144. libNameInc++;
  145. }
  146. wxString pluginType = SCH_IO_MGR::ShowType( SCH_IO_MGR::SCH_LEGACY );
  147. wxFileName fn = lib->GetFullFileName();
  148. // Use environment variable substitution where possible. This is based solely
  149. // on the internal user environment variable list. Checking against all of the
  150. // system wide environment variables is probably not a good idea.
  151. wxString fullFileName = NormalizePath( fn, &Pgm().GetLocalEnvVariables(), &Prj() );
  152. // Fall back to the absolute library path.
  153. if( fullFileName.IsEmpty() )
  154. fullFileName = lib->GetFullFileName();
  155. wxFileName tmpFn = fullFileName;
  156. // Don't add symbol libraries that do not exist.
  157. if( tmpFn.Normalize() && tmpFn.FileExists() )
  158. {
  159. msg.Printf( _( "Adding library \"%s\", file \"%s\" to project symbol library table." ),
  160. libName, fullFileName );
  161. aReporter.Report( msg, REPORTER::RPT_INFO );
  162. prjLibTable.InsertRow( new SYMBOL_LIB_TABLE_ROW( libName, fullFileName,
  163. pluginType ) );
  164. }
  165. else
  166. {
  167. msg.Printf( _( "Library \"%s\" not found." ), fullFileName );
  168. aReporter.Report( msg, REPORTER::RPT_WARNING );
  169. }
  170. }
  171. // Don't save empty project symbol library table.
  172. if( !prjLibTable.IsEmpty() )
  173. {
  174. wxFileName fn( Prj().GetProjectPath(), SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
  175. try
  176. {
  177. FILE_OUTPUTFORMATTER formatter( fn.GetFullPath() );
  178. prjLibTable.Format( &formatter, 0 );
  179. }
  180. catch( const IO_ERROR& ioe )
  181. {
  182. msg.Printf( _( "Failed to write project symbol library table. Error:\n %s" ),
  183. ioe.What() );
  184. aReporter.ReportTail( msg, REPORTER::RPT_ERROR );
  185. }
  186. aReporter.ReportTail( _( "Created project symbol library table.\n" ), REPORTER::RPT_INFO );
  187. }
  188. }
  189. }
  190. void DIALOG_SYMBOL_REMAP::remapSymbolsToLibTable( REPORTER& aReporter )
  191. {
  192. wxString msg;
  193. SCH_SCREENS schematic;
  194. SCH_COMPONENT* symbol;
  195. SCH_SCREEN* screen;
  196. for( screen = schematic.GetFirst(); screen; screen = schematic.GetNext() )
  197. {
  198. for( auto item : screen->Items().OfType( SCH_COMPONENT_T ) )
  199. {
  200. symbol = dynamic_cast<SCH_COMPONENT*>( item );
  201. if( !remapSymbolToLibTable( symbol ) )
  202. {
  203. msg.Printf( _( "No symbol \"%s\" found in symbol library table." ),
  204. symbol->GetLibId().GetLibItemName().wx_str() );
  205. aReporter.Report( msg, REPORTER::RPT_WARNING );
  206. }
  207. else
  208. {
  209. msg.Printf( _( "Symbol \"%s\" mapped to symbol library \"%s\"." ),
  210. symbol->GetLibId().GetLibItemName().wx_str(),
  211. symbol->GetLibId().GetLibNickname().wx_str() );
  212. aReporter.Report( msg, REPORTER::RPT_ACTION );
  213. screen->SetModify();
  214. }
  215. }
  216. }
  217. aReporter.Report( _( "Symbol library table mapping complete!" ), REPORTER::RPT_INFO );
  218. schematic.UpdateSymbolLinks( true );
  219. }
  220. bool DIALOG_SYMBOL_REMAP::remapSymbolToLibTable( SCH_COMPONENT* aSymbol )
  221. {
  222. wxCHECK_MSG( aSymbol != NULL, false, "Null pointer passed to remapSymbolToLibTable." );
  223. wxCHECK_MSG( aSymbol->GetLibId().GetLibNickname().empty(), false,
  224. "Cannot remap symbol that is already mapped." );
  225. wxCHECK_MSG( !aSymbol->GetLibId().GetLibItemName().empty(), false,
  226. "The symbol LIB_ID name is empty." );
  227. PART_LIBS* libs = Prj().SchLibs();
  228. for( PART_LIBS_BASE::iterator it = libs->begin(); it != libs->end(); ++it )
  229. {
  230. // Ignore the cache library.
  231. if( it->IsCache() )
  232. continue;
  233. LIB_PART* alias = it->FindPart( aSymbol->GetLibId().GetLibItemName().wx_str() );
  234. // Found in the same library as the old look up method assuming the user didn't
  235. // change the libraries or library ordering since the last time the schematic was
  236. // loaded.
  237. if( alias )
  238. {
  239. // Find the same library in the symbol library table using the full path and file name.
  240. wxString libFileName = it->GetFullFileName();
  241. const LIB_TABLE_ROW* row = Prj().SchSymbolLibTable()->FindRowByURI( libFileName );
  242. if( row )
  243. {
  244. LIB_ID id = aSymbol->GetLibId();
  245. id.SetLibNickname( row->GetNickName() );
  246. // Don't resolve symbol library links now.
  247. aSymbol->SetLibId( id, nullptr, nullptr );
  248. return true;
  249. }
  250. }
  251. }
  252. return false;
  253. }
  254. bool DIALOG_SYMBOL_REMAP::backupProject( REPORTER& aReporter )
  255. {
  256. static wxString backupFolder = "rescue-backup";
  257. wxString tmp;
  258. wxString errorMsg;
  259. wxFileName srcFileName;
  260. wxFileName destFileName;
  261. wxFileName backupPath;
  262. SCH_SCREENS schematic;
  263. // Copy backup files to different folder so as not to pollute the project folder.
  264. destFileName.SetPath( Prj().GetProjectPath() );
  265. destFileName.AppendDir( backupFolder );
  266. backupPath = destFileName;
  267. if( !destFileName.DirExists() )
  268. {
  269. if( !destFileName.Mkdir() )
  270. {
  271. errorMsg.Printf( _( "Cannot create project remap back up folder \"%s\"." ),
  272. destFileName.GetPath() );
  273. wxMessageDialog dlg( this, errorMsg, _( "Backup Error" ),
  274. wxYES_NO | wxCENTRE | wxRESIZE_BORDER | wxICON_QUESTION );
  275. dlg.SetYesNoLabels( wxMessageDialog::ButtonLabel( _( "Continue with Rescue" ) ),
  276. wxMessageDialog::ButtonLabel( _( "Abort Rescue" ) ) );
  277. if( dlg.ShowModal() == wxID_NO )
  278. return false;
  279. }
  280. }
  281. // Time stamp to append to file name in case multiple remappings are performed.
  282. wxString timeStamp = wxDateTime::Now().Format( "-%Y-%m-%d-%H-%M-%S" );
  283. // Back up symbol library table.
  284. srcFileName.SetPath( Prj().GetProjectPath() );
  285. srcFileName.SetName( SYMBOL_LIB_TABLE::GetSymbolLibTableFileName() );
  286. destFileName = srcFileName;
  287. destFileName.AppendDir( backupFolder );
  288. destFileName.SetName( destFileName.GetName() + timeStamp );
  289. tmp.Printf( _( "Backing up file \"%s\" to file \"%s\"." ),
  290. srcFileName.GetFullPath(), destFileName.GetFullPath() );
  291. aReporter.Report( tmp, REPORTER::RPT_INFO );
  292. if( wxFileName::Exists( srcFileName.GetFullPath() )
  293. && !wxCopyFile( srcFileName.GetFullPath(), destFileName.GetFullPath() ) )
  294. {
  295. tmp.Printf( _( "Failed to back up file \"%s\".\n" ), srcFileName.GetFullPath() );
  296. errorMsg += tmp;
  297. }
  298. // Back up the schematic files.
  299. for( SCH_SCREEN* screen = schematic.GetFirst(); screen; screen = schematic.GetNext() )
  300. {
  301. destFileName = screen->GetFileName();
  302. destFileName.SetName( destFileName.GetName() + timeStamp );
  303. // Check for nest hierarchical schematic paths.
  304. if( destFileName.GetPath() != backupPath.GetPath() )
  305. {
  306. destFileName.SetPath( backupPath.GetPath() );
  307. wxArrayString srcDirs = wxFileName( screen->GetFileName() ).GetDirs();
  308. wxArrayString destDirs = wxFileName( Prj().GetProjectPath() ).GetDirs();
  309. for( size_t i = destDirs.GetCount(); i < srcDirs.GetCount(); i++ )
  310. destFileName.AppendDir( srcDirs[i] );
  311. }
  312. else
  313. {
  314. destFileName.AppendDir( backupFolder );
  315. }
  316. tmp.Printf( _( "Backing up file \"%s\" to file \"%s\"." ),
  317. screen->GetFileName(), destFileName.GetFullPath() );
  318. aReporter.Report( tmp, REPORTER::RPT_INFO );
  319. if( !destFileName.DirExists() && !destFileName.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
  320. {
  321. tmp.Printf( _( "Failed to create backup folder \"%s\"\n" ), destFileName.GetPath() );
  322. errorMsg += tmp;
  323. continue;
  324. }
  325. if( wxFileName::Exists( screen->GetFileName() )
  326. && !wxCopyFile( screen->GetFileName(), destFileName.GetFullPath() ) )
  327. {
  328. tmp.Printf( _( "Failed to back up file \"%s\".\n" ), screen->GetFileName() );
  329. errorMsg += tmp;
  330. }
  331. }
  332. // Back up the project file.
  333. destFileName = Prj().GetProjectFullName();
  334. destFileName.SetName( destFileName.GetName() + timeStamp );
  335. destFileName.AppendDir( backupFolder );
  336. tmp.Printf( _( "Backing up file \"%s\" to file \"%s\"." ),
  337. Prj().GetProjectFullName(), destFileName.GetFullPath() );
  338. aReporter.Report( tmp, REPORTER::RPT_INFO );
  339. if( wxFileName::Exists( Prj().GetProjectFullName() )
  340. && !wxCopyFile( Prj().GetProjectFullName(), destFileName.GetFullPath() ) )
  341. {
  342. tmp.Printf( _( "Failed to back up file \"%s\".\n" ), Prj().GetProjectFullName() );
  343. errorMsg += tmp;
  344. }
  345. // Back up the cache library.
  346. srcFileName.SetPath( Prj().GetProjectPath() );
  347. srcFileName.SetName( Prj().GetProjectName() + "-cache" );
  348. srcFileName.SetExt( SchematicLibraryFileExtension );
  349. destFileName = srcFileName;
  350. destFileName.SetName( destFileName.GetName() + timeStamp );
  351. destFileName.AppendDir( backupFolder );
  352. tmp.Printf( _( "Backing up file \"%s\" to file \"%s\"." ),
  353. srcFileName.GetFullPath(), destFileName.GetFullPath() );
  354. aReporter.Report( tmp, REPORTER::RPT_INFO );
  355. if( srcFileName.Exists()
  356. && !wxCopyFile( srcFileName.GetFullPath(), destFileName.GetFullPath() ) )
  357. {
  358. tmp.Printf( _( "Failed to back up file \"%s\".\n" ), srcFileName.GetFullPath() );
  359. errorMsg += tmp;
  360. }
  361. // Back up the rescue symbol library if it exists.
  362. srcFileName.SetName( Prj().GetProjectName() + "-rescue" );
  363. destFileName.SetName( srcFileName.GetName() + timeStamp );
  364. tmp.Printf( _( "Backing up file \"%s\" to file \"%s\"." ),
  365. srcFileName.GetFullPath(), destFileName.GetFullPath() );
  366. aReporter.Report( tmp, REPORTER::RPT_INFO );
  367. if( srcFileName.Exists()
  368. && !wxCopyFile( srcFileName.GetFullPath(), destFileName.GetFullPath() ) )
  369. {
  370. tmp.Printf( _( "Failed to back up file \"%s\".\n" ), srcFileName.GetFullPath() );
  371. errorMsg += tmp;
  372. }
  373. // Back up the rescue symbol library document file if it exists.
  374. srcFileName.SetExt( "dcm" );
  375. destFileName.SetExt( srcFileName.GetExt() );
  376. tmp.Printf( _( "Backing up file \"%s\" to file \"%s\"." ),
  377. srcFileName.GetFullPath(), destFileName.GetFullPath() );
  378. aReporter.Report( tmp, REPORTER::RPT_INFO );
  379. if( srcFileName.Exists()
  380. && !wxCopyFile( srcFileName.GetFullPath(), destFileName.GetFullPath() ) )
  381. {
  382. tmp.Printf( _( "Failed to back up file \"%s\".\n" ), srcFileName.GetFullPath() );
  383. errorMsg += tmp;
  384. }
  385. if( !errorMsg.IsEmpty() )
  386. {
  387. wxMessageDialog dlg( this, _( "Some of the project files could not be backed up." ),
  388. _( "Backup Error" ),
  389. wxYES_NO | wxCENTRE | wxRESIZE_BORDER | wxICON_QUESTION );
  390. errorMsg.Trim();
  391. dlg.SetExtendedMessage( errorMsg );
  392. dlg.SetYesNoLabels( wxMessageDialog::ButtonLabel( _( "Continue with Rescue" ) ),
  393. wxMessageDialog::ButtonLabel( _( "Abort Rescue" ) ) );
  394. if( dlg.ShowModal() == wxID_NO )
  395. return false;
  396. }
  397. return true;
  398. }
  399. void DIALOG_SYMBOL_REMAP::OnUpdateUIRemapButton( wxUpdateUIEvent& aEvent )
  400. {
  401. aEvent.Enable( !m_remapped );
  402. }