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.

582 lines
16 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2020 CERN
  5. *
  6. * @author Wayne Stambaugh <stambaughw@gmail.com>
  7. *
  8. * This program is free software: you can redistribute it and/or modify it
  9. * under the terms of the GNU General Public License as published by the
  10. * Free Software Foundation, either version 3 of the License, or (at your
  11. * option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful, but
  14. * WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  16. * General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License along
  19. * with this program. If not, see <http://www.gnu.org/licenses/>.
  20. */
  21. #include <algorithm>
  22. #include <bitmaps.h>
  23. #include <kicad_string.h> // WildCompareString
  24. #include <kiway.h>
  25. #include <lib_id.h>
  26. #include <core/kicad_algo.h>
  27. #include <dialog_change_symbols.h>
  28. #include <sch_component.h>
  29. #include <sch_edit_frame.h>
  30. #include <sch_screen.h>
  31. #include <sch_sheet_path.h>
  32. #include <schematic.h>
  33. #include <template_fieldnames.h>
  34. #include <wx_html_report_panel.h>
  35. bool g_removeExtraFields = false;
  36. bool g_resetEmptyFields = false;
  37. bool g_resetFieldVisibilities = false;
  38. bool g_resetFieldEffects = false;
  39. bool g_resetFieldPositions = false;
  40. DIALOG_CHANGE_SYMBOLS::DIALOG_CHANGE_SYMBOLS( SCH_EDIT_FRAME* aParent, SCH_COMPONENT* aSymbol,
  41. MODE aMode ) :
  42. DIALOG_CHANGE_SYMBOLS_BASE( aParent ),
  43. m_symbol( aSymbol),
  44. m_mode( aMode )
  45. {
  46. wxASSERT( aParent );
  47. if( m_mode == MODE::UPDATE )
  48. {
  49. m_newIdSizer->Show( false );
  50. }
  51. else
  52. {
  53. m_matchAll->SetLabel( _( "Change all symbols in schematic" ) );
  54. SetTitle( _( "Change Symbols" ) );
  55. m_matchSizer->FindItem( m_matchAll )->Show( false );
  56. }
  57. if( m_symbol )
  58. {
  59. SCH_SHEET_PATH* currentSheet = &aParent->Schematic().CurrentSheet();
  60. if( m_mode == MODE::CHANGE )
  61. m_matchBySelection->SetLabel( _( "Change selected Symbol" ) );
  62. m_newId->AppendText( FROM_UTF8( m_symbol->GetLibId().Format().c_str() ) );
  63. m_specifiedReference->ChangeValue( m_symbol->GetRef( currentSheet ) );
  64. m_specifiedValue->ChangeValue( m_symbol->GetValue( currentSheet ) );
  65. m_specifiedId->ChangeValue( FROM_UTF8( m_symbol->GetLibId().Format().c_str() ) );
  66. }
  67. else
  68. {
  69. m_matchSizer->FindItem( m_matchBySelection )->Show( false );
  70. }
  71. m_matchIdBrowserButton->SetBitmap( KiBitmap( small_library_xpm ) );
  72. m_newIdBrowserButton->SetBitmap( KiBitmap( small_library_xpm ) );
  73. if( m_mode == MODE::CHANGE )
  74. {
  75. m_matchByReference->SetLabel( _( "Change symbols matching reference designator:" ) );
  76. m_matchByValue->SetLabel( _( "Change symbols matching value:" ) );
  77. m_matchById->SetLabel( _( "Change symbols matching library identifier:" ) );
  78. }
  79. m_matchSizer->SetEmptyCellSize( wxSize( 0, 0 ) );
  80. m_matchSizer->Layout();
  81. for( int i = 0; i < MANDATORY_FIELDS; ++i )
  82. {
  83. m_fieldsBox->Append( TEMPLATE_FIELDNAME::GetDefaultFieldName( i ) );
  84. if( i != REFERENCE && i != VALUE )
  85. m_fieldsBox->Check( i, true );
  86. }
  87. updateFieldsList();
  88. m_messagePanel->SetLazyUpdate( true );
  89. if( aSymbol && aSymbol->IsSelected() )
  90. {
  91. m_matchBySelection->SetValue( true );
  92. }
  93. else
  94. {
  95. if( aMode == MODE::UPDATE )
  96. m_matchAll->SetValue( true );
  97. else
  98. m_matchByReference->SetValue( true );
  99. }
  100. if( m_mode == MODE::CHANGE )
  101. {
  102. m_updateFieldsSizer->GetStaticBox()->SetLabel( _( "Update Fields") );
  103. m_removeExtraBox->SetLabel( _( "Remove fields if not in new symbol" ) );
  104. m_resetEmptyFields->SetLabel( _( "Reset fields if empty in new symbol" ) );
  105. m_resetFieldVisibilities->SetLabel( _( "Update field visibilities" ) );
  106. m_resetFieldEffects->SetLabel( _( "Update field sizes and styles" ) );
  107. m_resetFieldPositions->SetLabel( _( "Update field positions" ) );
  108. }
  109. m_removeExtraBox->SetValue( g_removeExtraFields );
  110. m_resetEmptyFields->SetValue( g_resetEmptyFields );
  111. m_resetFieldVisibilities->SetValue( g_resetFieldVisibilities );
  112. m_resetFieldEffects->SetValue( g_resetFieldEffects );
  113. m_resetFieldPositions->SetValue( g_resetFieldPositions );
  114. // DIALOG_SHIM needs a unique hash_key because classname is not sufficient
  115. // because the update and change versions of this dialog have different controls.
  116. m_hash_key = TO_UTF8( GetTitle() );
  117. // Ensure m_closeButton (with id = wxID_CANCEL) has the right label
  118. // (to fix automatic renaming of button label )
  119. m_sdbSizerCancel->SetLabel( _( "Close" ) );
  120. if( m_mode == MODE::CHANGE )
  121. m_sdbSizerOK->SetLabel( _( "Change" ) );
  122. else
  123. m_sdbSizerOK->SetLabel( _( "Update" ) );
  124. m_sdbSizerOK->SetDefault();
  125. // Now all widgets have the size fixed, call FinishDialogSettings
  126. FinishDialogSettings();
  127. }
  128. void DIALOG_CHANGE_SYMBOLS::onMatchByAll( wxCommandEvent& aEvent )
  129. {
  130. updateFieldsList();
  131. }
  132. void DIALOG_CHANGE_SYMBOLS::onMatchBySelected( wxCommandEvent& aEvent )
  133. {
  134. updateFieldsList();
  135. }
  136. void DIALOG_CHANGE_SYMBOLS::onMatchByReference( wxCommandEvent& aEvent )
  137. {
  138. updateFieldsList();
  139. m_specifiedReference->SetFocus();
  140. }
  141. void DIALOG_CHANGE_SYMBOLS::onMatchByValue( wxCommandEvent& aEvent )
  142. {
  143. updateFieldsList();
  144. m_specifiedValue->SetFocus();
  145. }
  146. void DIALOG_CHANGE_SYMBOLS::onMatchById( wxCommandEvent& aEvent )
  147. {
  148. updateFieldsList();
  149. m_specifiedId->SetFocus();
  150. }
  151. void DIALOG_CHANGE_SYMBOLS::onMatchTextKillFocus( wxFocusEvent& event )
  152. {
  153. updateFieldsList();
  154. }
  155. DIALOG_CHANGE_SYMBOLS::~DIALOG_CHANGE_SYMBOLS()
  156. {
  157. g_removeExtraFields = m_removeExtraBox->GetValue();
  158. g_resetEmptyFields = m_resetEmptyFields->GetValue();
  159. g_resetFieldVisibilities = m_resetFieldVisibilities->GetValue();
  160. g_resetFieldEffects = m_resetFieldEffects->GetValue();
  161. g_resetFieldPositions = m_resetFieldPositions->GetValue();
  162. }
  163. void DIALOG_CHANGE_SYMBOLS::launchMatchIdSymbolBrowser( wxCommandEvent& aEvent )
  164. {
  165. wxString newName = m_specifiedId->GetValue();
  166. KIWAY_PLAYER* frame = Kiway().Player( FRAME_SCH_VIEWER_MODAL, true );
  167. if( frame->ShowModal( &newName, this ) )
  168. {
  169. m_specifiedId->SetValue( newName );
  170. updateFieldsList();
  171. }
  172. frame->Destroy();
  173. }
  174. void DIALOG_CHANGE_SYMBOLS::launchNewIdSymbolBrowser( wxCommandEvent& aEvent )
  175. {
  176. wxString newName = m_newId->GetValue();
  177. KIWAY_PLAYER* frame = Kiway().Player( FRAME_SCH_VIEWER_MODAL, true );
  178. if( frame->ShowModal( &newName, this ) )
  179. {
  180. m_newId->SetValue( newName );
  181. updateFieldsList();
  182. }
  183. frame->Destroy();
  184. }
  185. void DIALOG_CHANGE_SYMBOLS::updateFieldsList()
  186. {
  187. SCH_EDIT_FRAME* frame = dynamic_cast<SCH_EDIT_FRAME*>( GetParent() );
  188. wxCHECK( frame, /* void */ );
  189. LIB_ID newId;
  190. SCH_SHEET_LIST hierarchy = frame->Schematic().GetSheets();
  191. if( m_mode == MODE::CHANGE )
  192. {
  193. newId.Parse( m_newId->GetValue(), LIB_ID::ID_SCH );
  194. if( !newId.IsValid() )
  195. return;
  196. }
  197. // Load new non-mandatory fields from the library parts of all matching symbols
  198. std::set<wxString> fieldNames;
  199. for( SCH_SHEET_PATH& instance : hierarchy )
  200. {
  201. SCH_SCREEN* screen = instance.LastScreen();
  202. wxCHECK2( screen, continue );
  203. for( SCH_ITEM* item : screen->Items().OfType( SCH_COMPONENT_T ) )
  204. {
  205. SCH_COMPONENT* symbol = dynamic_cast<SCH_COMPONENT*>( item );
  206. wxCHECK2( symbol, continue );
  207. if( !isMatch( symbol, &instance ) )
  208. continue;
  209. LIB_PART* libSymbol = frame->GetLibPart( symbol->GetLibId() );
  210. if( !libSymbol )
  211. continue;
  212. std::unique_ptr<LIB_PART> flattenedSymbol = libSymbol->Flatten();
  213. LIB_FIELDS libFields;
  214. flattenedSymbol->GetFields( libFields );
  215. for( unsigned i = MANDATORY_FIELDS; i < libFields.size(); ++i )
  216. fieldNames.insert( libFields[i].GetName() );
  217. }
  218. }
  219. // Update the listbox widget
  220. for( unsigned i = m_fieldsBox->GetCount() - 1; i >= MANDATORY_FIELDS; --i )
  221. m_fieldsBox->Delete( i );
  222. for( const wxString& fieldName : fieldNames )
  223. m_fieldsBox->Append( fieldName );
  224. for( unsigned i = MANDATORY_FIELDS; i < m_fieldsBox->GetCount(); ++i )
  225. m_fieldsBox->Check( i, true );
  226. }
  227. void DIALOG_CHANGE_SYMBOLS::checkAll( bool aCheck )
  228. {
  229. for( unsigned i = 0; i < m_fieldsBox->GetCount(); ++i )
  230. m_fieldsBox->Check( i, aCheck );
  231. }
  232. void DIALOG_CHANGE_SYMBOLS::onOkButtonClicked( wxCommandEvent& aEvent )
  233. {
  234. wxBusyCursor dummy;
  235. SCH_EDIT_FRAME* parent = dynamic_cast<SCH_EDIT_FRAME*>( GetParent() );
  236. wxCHECK( parent, /* void */ );
  237. m_messagePanel->Clear();
  238. m_messagePanel->Flush( false );
  239. // Create the set of fields to be updated
  240. m_updateFields.clear();
  241. for( unsigned i = 0; i < m_fieldsBox->GetCount(); ++i )
  242. {
  243. if( m_fieldsBox->IsChecked( i ) )
  244. m_updateFields.insert( m_fieldsBox->GetString( i ) );
  245. }
  246. if( processMatchingSymbols() )
  247. {
  248. parent->TestDanglingEnds(); // This will also redraw the changed symbols.
  249. parent->OnModify();
  250. }
  251. m_messagePanel->Flush( false );
  252. }
  253. bool DIALOG_CHANGE_SYMBOLS::isMatch( SCH_COMPONENT* aSymbol, SCH_SHEET_PATH* aInstance )
  254. {
  255. LIB_ID id;
  256. wxCHECK( aSymbol, false );
  257. SCH_EDIT_FRAME* frame = dynamic_cast<SCH_EDIT_FRAME*>( GetParent() );
  258. wxCHECK( frame, false );
  259. if( m_matchAll->GetValue() )
  260. {
  261. return true;
  262. }
  263. else if( m_matchBySelection->GetValue() )
  264. {
  265. return aSymbol == m_symbol;
  266. }
  267. else if( m_matchByReference->GetValue() )
  268. {
  269. return WildCompareString( m_specifiedReference->GetValue(), aSymbol->GetRef( aInstance ),
  270. false );
  271. }
  272. else if( m_matchByValue->GetValue() )
  273. {
  274. return WildCompareString( m_specifiedValue->GetValue(), aSymbol->GetValue( aInstance ),
  275. false );
  276. }
  277. else if( m_matchById )
  278. {
  279. id.Parse( m_specifiedId->GetValue(), LIB_ID::ID_SCH );
  280. return aSymbol->GetLibId() == id;
  281. }
  282. return false;
  283. }
  284. bool DIALOG_CHANGE_SYMBOLS::processMatchingSymbols()
  285. {
  286. SCH_EDIT_FRAME* frame = dynamic_cast<SCH_EDIT_FRAME*>( GetParent() );
  287. wxCHECK( frame, false );
  288. LIB_ID newId;
  289. bool appendToUndo = false;
  290. bool changed = false;
  291. SCH_SHEET_LIST hierarchy = frame->Schematic().GetSheets();
  292. if( m_mode == MODE::CHANGE )
  293. {
  294. newId.Parse( m_newId->GetValue(), LIB_ID::ID_SCH );
  295. if( !newId.IsValid() )
  296. return false;
  297. }
  298. // Use map as a cheap and dirty way to ensure library symbols are not updated multiple
  299. // times in complex hierachies.
  300. std::map<SCH_COMPONENT*, SCH_SCREEN*> symbolsToProcess;
  301. for( SCH_SHEET_PATH& instance : hierarchy )
  302. {
  303. SCH_SCREEN* screen = instance.LastScreen();
  304. wxCHECK2( screen, continue );
  305. for( SCH_ITEM* item : screen->Items().OfType( SCH_COMPONENT_T ) )
  306. {
  307. SCH_COMPONENT* symbol = dynamic_cast<SCH_COMPONENT*>( item );
  308. wxCHECK2( symbol, continue );
  309. if( !isMatch( symbol, &instance ) )
  310. continue;
  311. // Shared symbols always have identical library symbols so don't add duplicates.
  312. symbolsToProcess[symbol] = screen;
  313. }
  314. }
  315. for( auto i : symbolsToProcess )
  316. {
  317. SCH_COMPONENT* symbol = i.first;
  318. if( m_mode == MODE::UPDATE )
  319. {
  320. if( processSymbol( symbol, i.second, symbol->GetLibId(), appendToUndo ) )
  321. changed = true;
  322. }
  323. else
  324. {
  325. if( processSymbol( symbol, i.second, newId, appendToUndo ) )
  326. changed = true;
  327. }
  328. if( changed )
  329. appendToUndo = true;
  330. }
  331. return changed;
  332. }
  333. bool DIALOG_CHANGE_SYMBOLS::processSymbol( SCH_COMPONENT* aSymbol, SCH_SCREEN* aScreen,
  334. const LIB_ID& aNewId, bool aAppendToUndo )
  335. {
  336. wxCHECK( aSymbol, false );
  337. wxCHECK( aScreen, false );
  338. wxCHECK( aNewId.IsValid(), false );
  339. SCH_EDIT_FRAME* frame = dynamic_cast<SCH_EDIT_FRAME*>( GetParent() );
  340. wxCHECK( frame, false );
  341. LIB_ID oldId = aSymbol->GetLibId();
  342. wxString msg;
  343. wxString references;
  344. for( COMPONENT_INSTANCE_REFERENCE instance : aSymbol->GetInstanceReferences() )
  345. {
  346. if( references.IsEmpty() )
  347. references = instance.m_Reference;
  348. else
  349. references += " " + instance.m_Reference;
  350. }
  351. msg.Printf( _( "%s %s \"%s\" from \"%s\" to \"%s\"" ),
  352. ( m_mode == MODE::UPDATE ) ? _( "Update" ) : _( "Change" ),
  353. ( aSymbol->GetInstanceReferences().size() == 1 ) ? _( "symbol" ) : _( "symbols" ),
  354. references,
  355. oldId.Format().c_str(),
  356. aNewId.Format().c_str() );
  357. LIB_PART* libSymbol = frame->GetLibPart( aNewId );
  358. if( !libSymbol )
  359. {
  360. msg << ": " << _( "*** symbol not found ***" );
  361. m_messagePanel->Report( msg, RPT_SEVERITY_ERROR );
  362. return false;
  363. }
  364. std::unique_ptr< LIB_PART > flattenedSymbol = libSymbol->Flatten();
  365. if( flattenedSymbol->GetUnitCount() < aSymbol->GetUnit() )
  366. {
  367. msg << ": " << _( "*** new symbol has too few units ***" );
  368. m_messagePanel->Report( msg, RPT_SEVERITY_ERROR );
  369. return false;
  370. }
  371. // Removing the symbol needs to be done before the LIB_PART is changed to prevent stale
  372. // library symbols in the schematic file.
  373. aScreen->Remove( aSymbol );
  374. frame->SaveCopyInUndoList( aScreen, aSymbol, UNDO_REDO::CHANGED, aAppendToUndo );
  375. if( aNewId != aSymbol->GetLibId() )
  376. aSymbol->SetLibId( aNewId );
  377. aSymbol->SetLibSymbol( flattenedSymbol.release() );
  378. bool removeExtras = m_removeExtraBox->GetValue();
  379. bool resetEmpty = m_resetEmptyFields->GetValue();
  380. bool resetVis = m_resetFieldVisibilities->GetValue();
  381. bool resetEffects = m_resetFieldEffects->GetValue();
  382. bool resetPositions = m_resetFieldPositions->GetValue();
  383. for( unsigned i = 0; i < aSymbol->GetFields().size(); ++i )
  384. {
  385. SCH_FIELD* field = aSymbol->GetField( (int) i ) ;
  386. LIB_FIELD* libField = nullptr;
  387. if( !alg::contains( m_updateFields, field->GetName() ) )
  388. continue;
  389. if( i < MANDATORY_FIELDS )
  390. libField = libSymbol->GetField( (int) i );
  391. else
  392. libField = libSymbol->FindField( field->GetName() );
  393. if( libField )
  394. {
  395. if( libField->GetText().IsEmpty() )
  396. {
  397. if( resetEmpty )
  398. field->SetText( wxEmptyString );
  399. }
  400. else
  401. {
  402. field->SetText( libField->GetText() );
  403. }
  404. if( resetVis )
  405. field->SetVisible( libField->IsVisible() );
  406. if( resetEffects )
  407. {
  408. // Careful: the visible bit is also in Effects
  409. bool visible = field->IsVisible();
  410. field->SetEffects( *libField );
  411. field->SetVisible( visible );
  412. }
  413. if( resetPositions )
  414. {
  415. field->SetTextPos( aSymbol->GetPosition() + libField->GetTextPos() );
  416. }
  417. }
  418. else if( i >= MANDATORY_FIELDS && removeExtras )
  419. {
  420. aSymbol->RemoveField( field->GetName() );
  421. i--;
  422. }
  423. }
  424. LIB_FIELDS libFields;
  425. libSymbol->GetFields( libFields );
  426. for( unsigned i = MANDATORY_FIELDS; i < libFields.size(); ++i )
  427. {
  428. LIB_FIELD& libField = libFields[i];
  429. if( !alg::contains( m_updateFields, libField.GetName() ) )
  430. continue;
  431. if( !aSymbol->FindField( libField.GetName(), false ) )
  432. {
  433. wxString fieldName = libField.GetCanonicalName();
  434. SCH_FIELD newField( wxPoint( 0, 0), aSymbol->GetFieldCount(), aSymbol, fieldName );
  435. SCH_FIELD* schField = aSymbol->AddField( newField );
  436. schField->SetEffects( libField );
  437. schField->SetText( libField.GetText() );
  438. schField->SetTextPos( aSymbol->GetPosition() + libField.GetTextPos() );
  439. }
  440. }
  441. aScreen->Append( aSymbol );
  442. frame->GetCanvas()->GetView()->Update( aSymbol );
  443. msg += ": OK";
  444. m_messagePanel->Report( msg, RPT_SEVERITY_ACTION );
  445. return true;
  446. }