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.

507 lines
16 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 <sch_design_block_pane.h>
  28. #include <sch_edit_frame.h>
  29. #include <sch_group.h>
  30. #include <wx/choicdlg.h>
  31. #include <wx/msgdlg.h>
  32. #include <wx/textdlg.h>
  33. #include <wildcards_and_files_ext.h>
  34. #include <paths.h>
  35. #include <env_paths.h>
  36. #include <common.h>
  37. #include <kidialog.h>
  38. #include <confirm.h>
  39. #include <tool/tool_manager.h>
  40. #include <sch_selection_tool.h>
  41. #include <dialogs/dialog_design_block_properties.h>
  42. #include <json_common.h>
  43. bool checkOverwriteDb( wxWindow* aFrame, wxString& libname, wxString& newName )
  44. {
  45. wxString msg = wxString::Format( _( "Design block '%s' already exists in library '%s'." ), newName.GetData(),
  46. libname.GetData() );
  47. if( OKOrCancelDialog( aFrame, _( "Confirmation" ), msg, _( "Overwrite existing design block?" ), _( "Overwrite" ) )
  48. != wxID_OK )
  49. {
  50. return false;
  51. }
  52. return true;
  53. }
  54. bool checkOverwriteDbSchematic( wxWindow* aFrame, const LIB_ID& aLibId )
  55. {
  56. wxString msg =
  57. wxString::Format( _( "Design block '%s' already has a schematic." ), aLibId.GetUniStringLibItemName() );
  58. if( OKOrCancelDialog( aFrame, _( "Confirmation" ), msg, _( "Overwrite existing schematic?" ), _( "Overwrite" ) )
  59. != wxID_OK )
  60. {
  61. return false;
  62. }
  63. return true;
  64. }
  65. bool SCH_EDIT_FRAME::SaveSheetAsDesignBlock( const wxString& aLibraryName, SCH_SHEET_PATH& aSheetPath )
  66. {
  67. // Make sure the user has selected a library to save into
  68. if( m_designBlocksPane->GetSelectedLibId().GetLibNickname().empty() )
  69. {
  70. DisplayErrorMessage( this, _( "Please select a library to save the design block to." ) );
  71. return false;
  72. }
  73. // Just block all attempts to create design blocks with nested sheets at this point
  74. std::vector<SCH_ITEM*> sheets;
  75. aSheetPath.LastScreen()->GetSheets( &sheets );
  76. if( !sheets.empty() )
  77. {
  78. DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
  79. return false;
  80. }
  81. DESIGN_BLOCK blk;
  82. wxFileName fn = wxFileNameFromPath( aSheetPath.Last()->GetName() );
  83. blk.SetLibId( LIB_ID( aLibraryName, fn.GetName() ) );
  84. // Copy all fields from the sheet to the design block
  85. std::vector<SCH_FIELD>& shFields = aSheetPath.Last()->GetFields();
  86. nlohmann::ordered_map<wxString, wxString> dbFields;
  87. for( SCH_FIELD& f : shFields )
  88. {
  89. if( f.GetId() == FIELD_T::SHEET_NAME || f.GetId() == FIELD_T::SHEET_FILENAME )
  90. continue;
  91. dbFields[f.GetCanonicalName()] = f.GetText();
  92. }
  93. blk.SetFields( dbFields );
  94. DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, &blk );
  95. if( dlg.ShowModal() != wxID_OK )
  96. return false;
  97. wxString libName = blk.GetLibId().GetLibNickname();
  98. wxString newName = blk.GetLibId().GetLibItemName();
  99. if( Prj().DesignBlockLibs()->DesignBlockExists( libName, newName ) && !checkOverwriteDb( this, libName, newName ) )
  100. {
  101. return false;
  102. }
  103. // Save a temporary copy of the schematic file, as the plugin is just going to move it
  104. wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
  105. if( !saveSchematicFile( aSheetPath.Last(), tempFile ) )
  106. {
  107. DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
  108. wxRemoveFile( tempFile );
  109. return false;
  110. }
  111. blk.SetSchematicFile( tempFile );
  112. bool success = false;
  113. try
  114. {
  115. success = Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk ) == DESIGN_BLOCK_LIB_TABLE::SAVE_OK;
  116. }
  117. catch( const IO_ERROR& ioe )
  118. {
  119. DisplayError( this, ioe.What() );
  120. }
  121. // Clean up the temporary file
  122. wxRemoveFile( tempFile );
  123. m_designBlocksPane->RefreshLibs();
  124. m_designBlocksPane->SelectLibId( blk.GetLibId() );
  125. return success;
  126. }
  127. bool SCH_EDIT_FRAME::SaveSheetToDesignBlock( const LIB_ID& aLibId, SCH_SHEET_PATH& aSheetPath )
  128. {
  129. // Make sure the user has selected a library to save into
  130. if( !Prj().DesignBlockLibs()->DesignBlockExists( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) )
  131. {
  132. DisplayErrorMessage( this, _( "Please select a design block to save the schematic to." ) );
  133. return false;
  134. }
  135. // Just block all attempts to create design blocks with nested sheets at this point
  136. std::vector<SCH_ITEM*> sheets;
  137. aSheetPath.LastScreen()->GetSheets( &sheets );
  138. if( !sheets.empty() )
  139. {
  140. DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
  141. return false;
  142. }
  143. DESIGN_BLOCK* blk = nullptr;
  144. try
  145. {
  146. blk = Prj().DesignBlockLibs()->DesignBlockLoad( aLibId.GetLibNickname(), aLibId.GetLibItemName() );
  147. }
  148. catch( const IO_ERROR& ioe )
  149. {
  150. DisplayError( this, ioe.What() );
  151. return false;
  152. }
  153. if( !blk->GetSchematicFile().IsEmpty() && !checkOverwriteDbSchematic( this, aLibId ) )
  154. {
  155. return false;
  156. }
  157. // Copy all fields from the sheet to the design block.
  158. // Note: this will overwrite any existing fields in the design block, but
  159. // will leave extra fields not in this source sheet alone.
  160. std::vector<SCH_FIELD>& shFields = aSheetPath.Last()->GetFields();
  161. nlohmann::ordered_map<wxString, wxString> dbFields = blk->GetFields();
  162. for( SCH_FIELD& f : shFields )
  163. {
  164. if( f.GetId() == FIELD_T::SHEET_NAME || f.GetId() == FIELD_T::SHEET_FILENAME )
  165. continue;
  166. dbFields[f.GetCanonicalName()] = f.GetText();
  167. }
  168. blk->SetFields( dbFields );
  169. DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, blk, true );
  170. if( dlg.ShowModal() != wxID_OK )
  171. return false;
  172. // Save a temporary copy of the schematic file, as the plugin is just going to move it
  173. wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
  174. if( !saveSchematicFile( aSheetPath.Last(), tempFile ) )
  175. {
  176. DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
  177. wxRemoveFile( tempFile );
  178. return false;
  179. }
  180. blk->SetSchematicFile( tempFile );
  181. bool success = false;
  182. try
  183. {
  184. success = Prj().DesignBlockLibs()->DesignBlockSave( aLibId.GetLibNickname(), blk )
  185. == DESIGN_BLOCK_LIB_TABLE::SAVE_OK;
  186. }
  187. catch( const IO_ERROR& ioe )
  188. {
  189. DisplayError( this, ioe.What() );
  190. }
  191. // Clean up the temporary file
  192. wxRemoveFile( tempFile );
  193. m_designBlocksPane->RefreshLibs();
  194. m_designBlocksPane->SelectLibId( blk->GetLibId() );
  195. return success;
  196. }
  197. bool SCH_EDIT_FRAME::SaveSelectionAsDesignBlock( const wxString& aLibraryName )
  198. {
  199. // Get all selected items
  200. SCH_SELECTION selection = m_toolManager->GetTool<SCH_SELECTION_TOOL>()->GetSelection();
  201. if( selection.Empty() )
  202. {
  203. DisplayErrorMessage( this, _( "Please select some items to save as a design block." ) );
  204. return false;
  205. }
  206. // Make sure the user has selected a library to save into
  207. if( m_designBlocksPane->GetSelectedLibId().GetLibNickname().empty() )
  208. {
  209. DisplayErrorMessage( this, _( "Please select a library to save the design block to." ) );
  210. return false;
  211. }
  212. // Just block all attempts to create design blocks with nested sheets at this point
  213. if( selection.HasType( SCH_SHEET_T ) )
  214. {
  215. if( selection.Size() == 1 )
  216. {
  217. SCH_SHEET* sheet = static_cast<SCH_SHEET*>( selection.Front() );
  218. SCH_SHEET_PATH curPath = GetCurrentSheet();
  219. curPath.push_back( sheet );
  220. SaveSheetAsDesignBlock( aLibraryName, curPath );
  221. }
  222. else
  223. DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
  224. return false;
  225. }
  226. DESIGN_BLOCK blk;
  227. wxFileName fn = wxFileNameFromPath( GetScreen()->GetFileName() );
  228. blk.SetLibId( LIB_ID( aLibraryName, fn.GetName() ) );
  229. DIALOG_DESIGN_BLOCK_PROPERTIES dlg( this, &blk );
  230. if( dlg.ShowModal() != wxID_OK )
  231. return false;
  232. wxString libName = blk.GetLibId().GetLibNickname();
  233. wxString newName = blk.GetLibId().GetLibItemName();
  234. if( Prj().DesignBlockLibs()->DesignBlockExists( libName, newName ) && !checkOverwriteDb( this, libName, newName ) )
  235. {
  236. return false;
  237. }
  238. // Create a temporary screen
  239. SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic );
  240. // Copy the selected items to the temporary screen
  241. for( EDA_ITEM* item : selection )
  242. {
  243. EDA_ITEM* copy = item->Clone();
  244. tempScreen->Append( static_cast<SCH_ITEM*>( copy ) );
  245. }
  246. // Create a sheet for the temporary screen
  247. SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic );
  248. tempSheet->SetScreen( tempScreen );
  249. // Save a temporary copy of the schematic file, as the plugin is just going to move it
  250. wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
  251. if( !saveSchematicFile( tempSheet, tempFile ) )
  252. {
  253. DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
  254. wxRemoveFile( tempFile );
  255. return false;
  256. }
  257. blk.SetSchematicFile( tempFile );
  258. bool success = false;
  259. try
  260. {
  261. success = Prj().DesignBlockLibs()->DesignBlockSave( aLibraryName, &blk ) == DESIGN_BLOCK_LIB_TABLE::SAVE_OK;
  262. }
  263. catch( const IO_ERROR& ioe )
  264. {
  265. DisplayError( this, ioe.What() );
  266. }
  267. // Clean up the temporaries
  268. wxRemoveFile( tempFile );
  269. // This will also delete the screen
  270. delete tempSheet;
  271. m_designBlocksPane->RefreshLibs();
  272. m_designBlocksPane->SelectLibId( blk.GetLibId() );
  273. return success;
  274. }
  275. bool SCH_EDIT_FRAME::SaveSelectionToDesignBlock( const LIB_ID& aLibId )
  276. {
  277. // Get all selected items
  278. SCH_SELECTION selection = m_toolManager->GetTool<SCH_SELECTION_TOOL>()->GetSelection();
  279. if( selection.Empty() )
  280. {
  281. DisplayErrorMessage( this, _( "Please select some items to save as a design block." ) );
  282. return false;
  283. }
  284. // Make sure the user has selected a library to save into
  285. if( !Prj().DesignBlockLibs()->DesignBlockExists( aLibId.GetLibNickname(), aLibId.GetLibItemName() ) )
  286. {
  287. DisplayErrorMessage( this, _( "Please select a design block to save the schematic to." ) );
  288. return false;
  289. }
  290. // Just block all attempts to create design blocks with nested sheets at this point
  291. if( selection.HasType( SCH_SHEET_T ) )
  292. {
  293. if( selection.Size() == 1 )
  294. {
  295. SCH_SHEET* sheet = static_cast<SCH_SHEET*>( selection.Front() );
  296. SCH_SHEET_PATH curPath = GetCurrentSheet();
  297. curPath.push_back( sheet );
  298. SaveSheetToDesignBlock( aLibId, curPath );
  299. }
  300. else
  301. DisplayErrorMessage( this, _( "Design blocks with nested sheets are not supported." ) );
  302. return false;
  303. }
  304. // If we have a single group, we want to strip the group and select the children
  305. SCH_GROUP* group = nullptr;
  306. if( selection.Size() == 1 )
  307. {
  308. EDA_ITEM* item = selection.Front();
  309. if( item->Type() == SCH_GROUP_T )
  310. {
  311. group = static_cast<SCH_GROUP*>( item );
  312. selection.Remove( group );
  313. // Don't recurse; if we have a group of groups the user probably intends the inner groups to be saved
  314. group->RunOnChildren( [&]( EDA_ITEM* aItem )
  315. {
  316. selection.Add( aItem );
  317. },
  318. RECURSE_MODE::NO_RECURSE );
  319. }
  320. }
  321. DESIGN_BLOCK* blk = nullptr;
  322. try
  323. {
  324. blk = Prj().DesignBlockLibs()->DesignBlockLoad( aLibId.GetLibNickname(), aLibId.GetLibItemName() );
  325. }
  326. catch( const IO_ERROR& ioe )
  327. {
  328. DisplayError( this, ioe.What() );
  329. return false;
  330. }
  331. if( !blk->GetSchematicFile().IsEmpty() && !checkOverwriteDbSchematic( this, aLibId ) )
  332. {
  333. return false;
  334. }
  335. // Create a temporary screen
  336. SCH_SCREEN* tempScreen = new SCH_SCREEN( m_schematic );
  337. auto cloneAndAdd =
  338. [&] ( EDA_ITEM* aItem ) -> SCH_ITEM*
  339. {
  340. if( !aItem->IsSCH_ITEM() )
  341. return nullptr;
  342. SCH_ITEM* copy = static_cast<SCH_ITEM*>( aItem->Clone() );
  343. tempScreen->Append( static_cast<SCH_ITEM*>( copy ) );
  344. return copy;
  345. };
  346. // Copy the selected items to the temporary board
  347. for( EDA_ITEM* item : selection )
  348. {
  349. // Remove parent group membership since we strip the first group layer
  350. if( SCH_ITEM* copy = cloneAndAdd( item ) )
  351. copy->SetParentGroup( nullptr );
  352. if( item->Type() == SCH_GROUP_T )
  353. {
  354. SCH_GROUP* innerGroup = static_cast<SCH_GROUP*>( item );
  355. // Groups also need their children copied
  356. innerGroup->RunOnChildren( cloneAndAdd, RECURSE_MODE::RECURSE );
  357. }
  358. }
  359. // Create a sheet for the temporary screen
  360. SCH_SHEET* tempSheet = new SCH_SHEET( m_schematic );
  361. tempSheet->SetScreen( tempScreen );
  362. // Save a temporary copy of the schematic file, as the plugin is just going to move it
  363. wxString tempFile = wxFileName::CreateTempFileName( "design_block" );
  364. if( !saveSchematicFile( tempSheet, tempFile ) )
  365. {
  366. DisplayErrorMessage( this, _( "Error saving temporary schematic file to create design block." ) );
  367. wxRemoveFile( tempFile );
  368. return false;
  369. }
  370. blk->SetSchematicFile( tempFile );
  371. bool success = false;
  372. try
  373. {
  374. success = Prj().DesignBlockLibs()->DesignBlockSave( aLibId.GetLibNickname(), blk )
  375. == DESIGN_BLOCK_LIB_TABLE::SAVE_OK;
  376. // If we had a group, we need to reselect it
  377. if( group )
  378. {
  379. selection.Clear();
  380. selection.Add( group );
  381. // If we didn't have a design block link before, add one for convenience
  382. if( !group->HasDesignBlockLink() )
  383. {
  384. SCH_COMMIT commit( m_toolManager );
  385. commit.Modify( group );
  386. group->SetDesignBlockLibId( aLibId );
  387. commit.Push( "Set Group Design Block Link" );
  388. }
  389. }
  390. }
  391. catch( const IO_ERROR& ioe )
  392. {
  393. DisplayError( this, ioe.What() );
  394. }
  395. // Clean up the temporaries
  396. wxRemoveFile( tempFile );
  397. // This will also delete the screen
  398. delete tempSheet;
  399. m_designBlocksPane->RefreshLibs();
  400. m_designBlocksPane->SelectLibId( blk->GetLibId() );
  401. return success;
  402. }