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.

644 lines
19 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU General Public License
  8. * as published by the Free Software Foundation; either version 2
  9. * of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. * GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License
  17. * along with this program; if not, you may find one here:
  18. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  19. * or you may search the http://www.gnu.org website for the version 2 license,
  20. * or you may write to the Free Software Foundation, Inc.,
  21. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  22. */
  23. #include <pgm_base.h>
  24. #include <kiway.h>
  25. #include <design_block.h>
  26. #include <design_block_lib_table.h>
  27. #include <design_block_pane.h>
  28. #include <sch_edit_frame.h>
  29. #include <wx/choicdlg.h>
  30. #include <wx/msgdlg.h>
  31. #include <wx/textdlg.h>
  32. #include <wildcards_and_files_ext.h>
  33. #include <paths.h>
  34. #include <env_paths.h>
  35. #include <common.h>
  36. #include <kidialog.h>
  37. #include <confirm.h>
  38. #include <tool/tool_manager.h>
  39. #include <sch_selection_tool.h>
  40. #include <dialogs/dialog_design_block_properties.h>
  41. #include <nlohmann/json.hpp>
  42. bool checkOverwrite( SCH_EDIT_FRAME* aFrame, wxString& libname, wxString& newName )
  43. {
  44. wxString msg = wxString::Format( _( "Design block '%s' already exists in library '%s'." ),
  45. newName.GetData(), libname.GetData() );
  46. if( OKOrCancelDialog( aFrame, _( "Confirmation" ), msg, _( "Overwrite existing design block?" ),
  47. _( "Overwrite" ) )
  48. != wxID_OK )
  49. {
  50. return false;
  51. }
  52. return true;
  53. }
  54. DESIGN_BLOCK_LIB_TABLE* SCH_EDIT_FRAME::selectDesignBlockLibTable( bool aOptional )
  55. {
  56. // If no project is loaded, always work with the global table
  57. if( Prj().IsNullProject() )
  58. {
  59. DESIGN_BLOCK_LIB_TABLE* ret = &DESIGN_BLOCK_LIB_TABLE::GetGlobalLibTable();
  60. if( aOptional )
  61. {
  62. wxMessageDialog dlg( this, _( "Add the library to the global library table?" ),
  63. _( "Add To Global Library Table" ), wxYES_NO );
  64. if( dlg.ShowModal() != wxID_OK )
  65. ret = nullptr;
  66. }
  67. return ret;
  68. }
  69. wxArrayString libTableNames;
  70. libTableNames.Add( _( "Global" ) );
  71. libTableNames.Add( _( "Project" ) );
  72. wxSingleChoiceDialog dlg( this, _( "Choose the Library Table to add the library to:" ),
  73. _( "Add To Library Table" ), libTableNames );
  74. if( aOptional )
  75. {
  76. dlg.FindWindow( wxID_CANCEL )->SetLabel( _( "Skip" ) );
  77. dlg.FindWindow( wxID_OK )->SetLabel( _( "Add" ) );
  78. }
  79. if( dlg.ShowModal() != wxID_OK )
  80. return nullptr;
  81. switch( dlg.GetSelection() )
  82. {
  83. case 0: return &DESIGN_BLOCK_LIB_TABLE::GetGlobalLibTable();
  84. case 1: return Prj().DesignBlockLibs();
  85. default: return nullptr;
  86. }
  87. }
  88. wxString SCH_EDIT_FRAME::CreateNewDesignBlockLibrary( const wxString& aLibName,
  89. const wxString& aProposedName )
  90. {
  91. return createNewDesignBlockLibrary( aLibName, aProposedName, selectDesignBlockLibTable() );
  92. }
  93. wxString SCH_EDIT_FRAME::createNewDesignBlockLibrary( const wxString& aLibName,
  94. const wxString& aProposedName,
  95. DESIGN_BLOCK_LIB_TABLE* aTable )
  96. {
  97. if( aTable == nullptr )
  98. return wxEmptyString;
  99. wxFileName fn;
  100. bool doAdd = false;
  101. bool isGlobal = ( aTable == &DESIGN_BLOCK_LIB_TABLE::GetGlobalLibTable() );
  102. wxString initialPath = aProposedName;
  103. if( initialPath.IsEmpty() )
  104. initialPath = isGlobal ? PATHS::GetDefaultUserDesignBlocksPath() : Prj().GetProjectPath();
  105. if( aLibName.IsEmpty() )
  106. {
  107. fn = initialPath;
  108. if( !LibraryFileBrowser( false, fn, FILEEXT::KiCadDesignBlockLibPathWildcard(),
  109. FILEEXT::KiCadDesignBlockLibPathExtension, false, isGlobal,
  110. initialPath ) )
  111. {
  112. return wxEmptyString;
  113. }
  114. doAdd = true;
  115. }
  116. else
  117. {
  118. fn = EnsureFileExtension( aLibName, FILEEXT::KiCadDesignBlockLibPathExtension );
  119. if( !fn.IsAbsolute() )
  120. {
  121. fn.SetName( aLibName );
  122. fn.MakeAbsolute( initialPath );
  123. }
  124. }
  125. // We can save libs only using DESIGN_BLOCK_IO_MGR::KICAD_SEXP format (.pretty libraries)
  126. DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T piType = DESIGN_BLOCK_IO_MGR::KICAD_SEXP;
  127. wxString libPath = fn.GetFullPath();
  128. try
  129. {
  130. IO_RELEASER<DESIGN_BLOCK_IO> pi( DESIGN_BLOCK_IO_MGR::FindPlugin( piType ) );
  131. bool writable = false;
  132. bool exists = false;
  133. try
  134. {
  135. writable = pi->IsLibraryWritable( libPath );
  136. exists = fn.Exists();
  137. }
  138. catch( const IO_ERROR& )
  139. {
  140. // best efforts....
  141. }
  142. if( exists )
  143. {
  144. if( !writable )
  145. {
  146. wxString msg = wxString::Format( _( "Library %s is read only." ), libPath );
  147. ShowInfoBarError( msg );
  148. return wxEmptyString;
  149. }
  150. else
  151. {
  152. wxString msg = wxString::Format( _( "Library %s already exists." ), libPath );
  153. KIDIALOG dlg( this, msg, _( "Confirmation" ), wxOK | wxCANCEL | wxICON_WARNING );
  154. dlg.SetOKLabel( _( "Overwrite" ) );
  155. dlg.DoNotShowCheckbox( __FILE__, __LINE__ );
  156. if( dlg.ShowModal() == wxID_CANCEL )
  157. return wxEmptyString;
  158. pi->DeleteLibrary( libPath );
  159. }
  160. }
  161. pi->CreateLibrary( libPath );
  162. }
  163. catch( const IO_ERROR& ioe )
  164. {
  165. DisplayError( this, ioe.What() );
  166. return wxEmptyString;
  167. }
  168. if( doAdd )
  169. AddDesignBlockLibrary( libPath, aTable );
  170. return libPath;
  171. }
  172. bool SCH_EDIT_FRAME::AddDesignBlockLibrary( const wxString& aFilename,
  173. DESIGN_BLOCK_LIB_TABLE* aTable )
  174. {
  175. if( aTable == nullptr )
  176. aTable = selectDesignBlockLibTable();
  177. if( aTable == nullptr )
  178. return wxEmptyString;
  179. bool isGlobal = ( aTable == &DESIGN_BLOCK_LIB_TABLE::GetGlobalLibTable() );
  180. wxFileName fn( aFilename );
  181. if( aFilename.IsEmpty() )
  182. {
  183. if( !LibraryFileBrowser( true, fn, FILEEXT::KiCadDesignBlockLibPathWildcard(),
  184. FILEEXT::KiCadDesignBlockLibPathExtension, true, isGlobal,
  185. PATHS::GetDefaultUserDesignBlocksPath() ) )
  186. {
  187. return false;
  188. }
  189. }
  190. wxString libPath = fn.GetFullPath();
  191. wxString libName = fn.GetName();
  192. if( libName.IsEmpty() )
  193. return false;
  194. // Open a dialog to ask for a description
  195. wxString description = wxGetTextFromUser( _( "Enter a description for the library:" ),
  196. _( "Library Description" ), wxEmptyString, this );
  197. DESIGN_BLOCK_IO_MGR::DESIGN_BLOCK_FILE_T lib_type =
  198. DESIGN_BLOCK_IO_MGR::GuessPluginTypeFromLibPath( libPath );
  199. if( lib_type == DESIGN_BLOCK_IO_MGR::FILE_TYPE_NONE )
  200. lib_type = DESIGN_BLOCK_IO_MGR::KICAD_SEXP;
  201. wxString type = DESIGN_BLOCK_IO_MGR::ShowType( lib_type );
  202. // KiCad lib is our default guess. So it might not have the .kicad_blocks extension
  203. // In this case, the extension is part of the library name
  204. if( lib_type == DESIGN_BLOCK_IO_MGR::KICAD_SEXP
  205. && fn.GetExt() != FILEEXT::KiCadDesignBlockLibPathExtension )
  206. libName = fn.GetFullName();
  207. // try to use path normalized to an environmental variable or project path
  208. wxString normalizedPath = NormalizePath( libPath, &Pgm().GetLocalEnvVariables(), &Prj() );
  209. try
  210. {
  211. DESIGN_BLOCK_LIB_TABLE_ROW* row = new DESIGN_BLOCK_LIB_TABLE_ROW(
  212. libName, normalizedPath, type, wxEmptyString, description );
  213. aTable->InsertRow( row );
  214. if( isGlobal )
  215. DESIGN_BLOCK_LIB_TABLE::GetGlobalLibTable().Save(
  216. DESIGN_BLOCK_LIB_TABLE::GetGlobalTableFileName() );
  217. else
  218. Prj().DesignBlockLibs()->Save( Prj().DesignBlockLibTblName() );
  219. }
  220. catch( const IO_ERROR& ioe )
  221. {
  222. DisplayError( this, ioe.What() );
  223. return false;
  224. }
  225. LIB_ID libID( libName, wxEmptyString );
  226. m_designBlocksPane->RefreshLibs();
  227. m_designBlocksPane->SelectLibId( libID );
  228. return true;
  229. }
  230. void SCH_EDIT_FRAME::SaveSheetAsDesignBlock( const wxString& aLibraryName,
  231. SCH_SHEET_PATH& aSheetPath )
  232. {
  233. // Make sure the user has selected a library to save into
  234. if( m_designBlocksPane->GetSelectedLibId().GetLibNickname().empty() )
  235. {
  236. DisplayErrorMessage( this, _( "Please select a library to save the design block to." ) );
  237. return;
  238. }
  239. // Just block all attempts to create design blocks with nested sheets at this point
  240. std::vector<SCH_ITEM*> sheets;
  241. aSheetPath.LastScreen()->GetSheets( &sheets );
  242. if( !sheets.empty() )
  243. {
  244. DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
  245. return;
  246. }
  247. DESIGN_BLOCK blk;
  248. wxFileName fn = wxFileNameFromPath( aSheetPath.Last()->GetName() );
  249. blk.SetLibId( LIB_ID( aLibraryName, fn.GetName() ) );
  250. // Copy all fields from the sheet to the design block
  251. std::vector<SCH_FIELD>& shFields = aSheetPath.Last()->GetFields();
  252. nlohmann::ordered_map<wxString, wxString> dbFields;
  253. for( int i = 0; i < (int) shFields.size(); i++ )
  254. {
  255. if( i == SHEETNAME || i == SHEETFILENAME )
  256. continue;
  257. dbFields[shFields[i].GetCanonicalName()] = shFields[i].GetText();
  258. }
  259. blk.SetFields( dbFields );
  260. DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, &blk );
  261. if( dlg.ShowModal() != wxID_OK )
  262. return;
  263. // Save a temporary copy of the schematic file, as the plugin is just going to move it
  264. wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
  265. if( !saveSchematicFile( aSheetPath.Last(), tempFile ) )
  266. {
  267. DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
  268. wxRemoveFile( tempFile );
  269. return;
  270. }
  271. blk.SetSchematicFile( tempFile );
  272. try
  273. {
  274. wxString libName = blk.GetLibId().GetLibNickname();
  275. wxString newName = blk.GetLibId().GetLibItemName();
  276. if( Prj().DesignBlockLibs()->DesignBlockExists( libName, newName ) )
  277. if( !checkOverwrite( this, libName, newName ) )
  278. return;
  279. Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk );
  280. }
  281. catch( const IO_ERROR& ioe )
  282. {
  283. DisplayError( this, ioe.What() );
  284. }
  285. // Clean up the temporary file
  286. wxRemoveFile( tempFile );
  287. m_designBlocksPane->RefreshLibs();
  288. m_designBlocksPane->SelectLibId( blk.GetLibId() );
  289. }
  290. void SCH_EDIT_FRAME::SaveSelectionAsDesignBlock( const wxString& aLibraryName )
  291. {
  292. // Make sure the user has selected a library to save into
  293. if( m_designBlocksPane->GetSelectedLibId().GetLibNickname().empty() )
  294. {
  295. DisplayErrorMessage( this, _( "Please select a library to save the design block to." ) );
  296. return;
  297. }
  298. // Get all selected items
  299. SCH_SELECTION selection = m_toolManager->GetTool<SCH_SELECTION_TOOL>()->GetSelection();
  300. if( selection.Empty() )
  301. {
  302. DisplayErrorMessage( this, _( "Please select some items to save as a design block." ) );
  303. return;
  304. }
  305. // Just block all attempts to create design blocks with nested sheets at this point
  306. if( selection.HasType( SCH_SHEET_T ) )
  307. {
  308. if( selection.Size() == 1 )
  309. {
  310. SCH_SHEET* sheet = static_cast<SCH_SHEET*>( selection.Front() );
  311. SCH_SHEET_PATH curPath = GetCurrentSheet();
  312. curPath.push_back( sheet );
  313. SaveSheetAsDesignBlock( aLibraryName, curPath );
  314. }
  315. else
  316. {
  317. DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
  318. }
  319. return;
  320. }
  321. DESIGN_BLOCK blk;
  322. wxFileName fn = wxFileNameFromPath( GetScreen()->GetFileName() );
  323. blk.SetLibId( LIB_ID( aLibraryName, fn.GetName() ) );
  324. DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, &blk );
  325. if( dlg.ShowModal() != wxID_OK )
  326. return;
  327. // Create a temporary screen
  328. SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic );
  329. // Copy the selected items to the temporary screen
  330. for( EDA_ITEM* item : selection )
  331. {
  332. EDA_ITEM* copy = item->Clone();
  333. tempScreen->Append( static_cast<SCH_ITEM*>( copy ) );
  334. }
  335. // Create a sheet for the temporary screen
  336. SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic );
  337. tempSheet->SetScreen( tempScreen );
  338. // Save a temporary copy of the schematic file, as the plugin is just going to move it
  339. wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
  340. if( !saveSchematicFile( tempSheet, tempFile ) )
  341. {
  342. DisplayErrorMessage( this,
  343. _( "Error saving temporary schematic file to create design block." ) );
  344. wxRemoveFile( tempFile );
  345. return;
  346. }
  347. blk.SetSchematicFile( tempFile );
  348. try
  349. {
  350. wxString libName = blk.GetLibId().GetLibNickname();
  351. wxString newName = blk.GetLibId().GetLibItemName();
  352. if( Prj().DesignBlockLibs()->DesignBlockExists( libName, newName ) )
  353. if( !checkOverwrite( this, libName, newName ) )
  354. return;
  355. Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk );
  356. }
  357. catch( const IO_ERROR& ioe )
  358. {
  359. DisplayError( this, ioe.What() );
  360. }
  361. // Clean up the temporaries
  362. wxRemoveFile( tempFile );
  363. // This will also delete the screen
  364. delete tempSheet;
  365. m_designBlocksPane->RefreshLibs();
  366. m_designBlocksPane->SelectLibId( blk.GetLibId() );
  367. }
  368. bool SCH_EDIT_FRAME::DeleteDesignBlockLibrary( const wxString& aLibName, bool aConfirm )
  369. {
  370. if( aLibName.IsEmpty() )
  371. {
  372. DisplayErrorMessage( this, _( "Please select a library to delete." ) );
  373. return false;
  374. }
  375. if( !Prj().DesignBlockLibs()->IsDesignBlockLibWritable( aLibName ) )
  376. {
  377. wxString msg = wxString::Format( _( "Library '%s' is read only." ), aLibName );
  378. ShowInfoBarError( msg );
  379. return false;
  380. }
  381. // Confirmation
  382. wxString msg = wxString::Format( _( "Delete design block library '%s' from disk? This will "
  383. "delete all design blocks within the library." ),
  384. aLibName.GetData() );
  385. if( aConfirm && !IsOK( this, msg ) )
  386. return false;
  387. try
  388. {
  389. Prj().DesignBlockLibs()->DesignBlockLibDelete( aLibName );
  390. }
  391. catch( const IO_ERROR& ioe )
  392. {
  393. DisplayError( this, ioe.What() );
  394. return false;
  395. }
  396. msg.Printf( _( "Design block library '%s' deleted" ), aLibName.GetData() );
  397. SetStatusText( msg );
  398. m_designBlocksPane->RefreshLibs();
  399. return true;
  400. }
  401. bool SCH_EDIT_FRAME::DeleteDesignBlockFromLibrary( const LIB_ID& aLibId, bool aConfirm )
  402. {
  403. if( !aLibId.IsValid() )
  404. return false;
  405. wxString libname = aLibId.GetLibNickname();
  406. wxString dbname = aLibId.GetLibItemName();
  407. if( !Prj().DesignBlockLibs()->IsDesignBlockLibWritable( libname ) )
  408. {
  409. wxString msg = wxString::Format( _( "Library '%s' is read only." ), libname );
  410. ShowInfoBarError( msg );
  411. return false;
  412. }
  413. // Confirmation
  414. wxString msg = wxString::Format( _( "Delete design block '%s' in library '%s' from disk?" ),
  415. dbname.GetData(), libname.GetData() );
  416. if( aConfirm && !IsOK( this, msg ) )
  417. return false;
  418. try
  419. {
  420. Prj().DesignBlockLibs()->DesignBlockDelete( libname, dbname );
  421. }
  422. catch( const IO_ERROR& ioe )
  423. {
  424. DisplayError( this, ioe.What() );
  425. return false;
  426. }
  427. msg.Printf( _( "Design block '%s' deleted from library '%s'" ), dbname.GetData(),
  428. libname.GetData() );
  429. SetStatusText( msg );
  430. m_designBlocksPane->RefreshLibs();
  431. return true;
  432. }
  433. bool SCH_EDIT_FRAME::EditDesignBlockProperties( const LIB_ID& aLibId )
  434. {
  435. if( !aLibId.IsValid() )
  436. return false;
  437. wxString libname = aLibId.GetLibNickname();
  438. wxString dbname = aLibId.GetLibItemName();
  439. if( !Prj().DesignBlockLibs()->IsDesignBlockLibWritable( libname ) )
  440. {
  441. wxString msg = wxString::Format( _( "Library '%s' is read only." ), libname );
  442. ShowInfoBarError( msg );
  443. return false;
  444. }
  445. DESIGN_BLOCK* designBlock = GetDesignBlock( aLibId, true, true );
  446. if( !designBlock )
  447. return false;
  448. wxString originalName = designBlock->GetLibId().GetLibItemName();
  449. DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, designBlock );
  450. if( dlg.ShowModal() != wxID_OK )
  451. return false;
  452. wxString newName = designBlock->GetLibId().GetLibItemName();
  453. try
  454. {
  455. if( originalName != newName )
  456. {
  457. if( Prj().DesignBlockLibs()->DesignBlockExists( libname, newName ) )
  458. if( !checkOverwrite( this, libname, newName ) )
  459. return false;
  460. Prj().DesignBlockLibs()->DesignBlockSave( libname, designBlock );
  461. Prj().DesignBlockLibs()->DesignBlockDelete( libname, originalName );
  462. }
  463. else
  464. {
  465. Prj().DesignBlockLibs()->DesignBlockSave( libname, designBlock );
  466. }
  467. }
  468. catch( const IO_ERROR& ioe )
  469. {
  470. DisplayError( this, ioe.What() );
  471. return false;
  472. }
  473. m_designBlocksPane->RefreshLibs();
  474. m_designBlocksPane->SelectLibId( designBlock->GetLibId() );
  475. return true;
  476. }
  477. DESIGN_BLOCK* SchGetDesignBlock( const LIB_ID& aLibId, DESIGN_BLOCK_LIB_TABLE* aLibTable,
  478. wxWindow* aParent, bool aShowErrorMsg )
  479. {
  480. wxCHECK_MSG( aLibTable, nullptr, wxS( "Invalid design block library table." ) );
  481. DESIGN_BLOCK* designBlock = nullptr;
  482. try
  483. {
  484. designBlock = aLibTable->DesignBlockLoadWithOptionalNickname( aLibId, true );
  485. }
  486. catch( const IO_ERROR& ioe )
  487. {
  488. if( aShowErrorMsg )
  489. {
  490. wxString msg = wxString::Format( _( "Error loading design block %s from "
  491. "library '%s'." ),
  492. aLibId.GetLibItemName().wx_str(),
  493. aLibId.GetLibNickname().wx_str() );
  494. DisplayErrorMessage( aParent, msg, ioe.What() );
  495. }
  496. }
  497. return designBlock;
  498. }
  499. DESIGN_BLOCK* SCH_EDIT_FRAME::GetDesignBlock( const LIB_ID& aLibId, bool aUseCacheLib,
  500. bool aShowErrorMsg )
  501. {
  502. return SchGetDesignBlock( aLibId, Prj().DesignBlockLibs(), this, aShowErrorMsg );
  503. }
  504. void SCH_EDIT_FRAME::UpdateDesignBlockOptions()
  505. {
  506. m_designBlocksPane->UpdateCheckboxes();
  507. }