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.

848 lines
27 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright 2017 Jean-Pierre Charras, jp.charras@wanadoo.fr
  5. * Copyright 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. /**
  25. * @file eeschema/dialogs/dialog_edit_symbols_libid.cpp
  26. * @brief Dialog to remap library id of symbols to another library id
  27. */
  28. #include <confirm.h>
  29. #include <sch_edit_frame.h>
  30. #include <sch_symbol.h>
  31. #include <sch_reference_list.h>
  32. #include <schematic.h>
  33. #include <symbol_lib_table.h>
  34. #include <trace_helpers.h>
  35. #include <widgets/wx_grid.h>
  36. #include <dialog_edit_symbols_libid_base.h>
  37. #include <wx/tokenzr.h>
  38. #include <wx/choicdlg.h>
  39. #include <wx/dcclient.h>
  40. #include <grid_tricks.h>
  41. #include <widgets/grid_text_button_helpers.h>
  42. #include <string_utils.h>
  43. #define COL_REFS 0
  44. #define COL_CURR_LIBID 1
  45. #define COL_NEW_LIBID 2
  46. // a re-implementation of wxGridCellAutoWrapStringRenderer to allow workaround to autorowsize bug
  47. class GRIDCELL_AUTOWRAP_STRINGRENDERER : public wxGridCellAutoWrapStringRenderer
  48. {
  49. public:
  50. int GetHeight( wxDC& aDC, wxGrid* aGrid, int aRow, int aCol );
  51. wxGridCellRenderer *Clone() const override
  52. { return new GRIDCELL_AUTOWRAP_STRINGRENDERER; }
  53. private:
  54. // HELPER ROUTINES UNCHANGED FROM wxWidgets IMPLEMENTATION
  55. wxArrayString GetTextLines( wxGrid& grid, wxDC& dc, const wxGridCellAttr& attr,
  56. const wxRect& rect, int row, int col );
  57. // Helper methods of GetTextLines()
  58. // Break a single logical line of text into several physical lines, all of
  59. // which are added to the lines array. The lines are broken at maxWidth and
  60. // the dc is used for measuring text extent only.
  61. void BreakLine( wxDC& dc, const wxString& logicalLine, wxCoord maxWidth, wxArrayString& lines );
  62. // Break a word, which is supposed to be wider than maxWidth, into several
  63. // lines, which are added to lines array and the last, incomplete, of which
  64. // is returned in line output parameter.
  65. //
  66. // Returns the width of the last line.
  67. wxCoord BreakWord( wxDC& dc, const wxString& word, wxCoord maxWidth, wxArrayString& lines,
  68. wxString& line );
  69. };
  70. // PRIVATE METHOD UNCHANGED FROM wxWidgets IMPLEMENTATION
  71. wxArrayString GRIDCELL_AUTOWRAP_STRINGRENDERER::GetTextLines( wxGrid& grid, wxDC& dc,
  72. const wxGridCellAttr& attr,
  73. const wxRect& rect, int row, int col )
  74. {
  75. dc.SetFont( attr.GetFont() );
  76. const wxCoord maxWidth = rect.GetWidth();
  77. // Transform logical lines into physical ones, wrapping the longer ones.
  78. const wxArrayString logicalLines = wxSplit( grid.GetCellValue( row, col ), '\n', '\0' );
  79. // Trying to do anything if the column is hidden anyhow doesn't make sense
  80. // and we run into problems in BreakLine() in this case.
  81. if( maxWidth <= 0 )
  82. return logicalLines;
  83. wxArrayString physicalLines;
  84. for( const wxString& line : logicalLines )
  85. {
  86. if( dc.GetTextExtent( line ).x > maxWidth )
  87. {
  88. // Line does not fit, break it up.
  89. BreakLine( dc, line, maxWidth, physicalLines );
  90. }
  91. else // The entire line fits as is
  92. {
  93. physicalLines.push_back( line );
  94. }
  95. }
  96. return physicalLines;
  97. }
  98. // PRIVATE METHOD UNCHANGED FROM wxWidgets IMPLEMENTATION
  99. void GRIDCELL_AUTOWRAP_STRINGRENDERER::BreakLine( wxDC& dc, const wxString& logicalLine,
  100. wxCoord maxWidth, wxArrayString& lines )
  101. {
  102. wxCoord lineWidth = 0;
  103. wxString line;
  104. // For each word
  105. wxStringTokenizer wordTokenizer( logicalLine, wxS( " \t" ), wxTOKEN_RET_DELIMS );
  106. while( wordTokenizer.HasMoreTokens() )
  107. {
  108. const wxString word = wordTokenizer.GetNextToken();
  109. const wxCoord wordWidth = dc.GetTextExtent( word ).x;
  110. if( lineWidth + wordWidth < maxWidth )
  111. {
  112. // Word fits, just add it to this line.
  113. line += word;
  114. lineWidth += wordWidth;
  115. }
  116. else
  117. {
  118. // Word does not fit, check whether the word is itself wider that
  119. // available width
  120. if( wordWidth < maxWidth )
  121. {
  122. // Word can fit in a new line, put it at the beginning
  123. // of the new line.
  124. lines.push_back( line );
  125. line = word;
  126. lineWidth = wordWidth;
  127. }
  128. else // Word cannot fit in available width at all.
  129. {
  130. if( !line.empty() )
  131. {
  132. lines.push_back( line );
  133. line.clear();
  134. lineWidth = 0;
  135. }
  136. // Break it up in several lines.
  137. lineWidth = BreakWord( dc, word, maxWidth, lines, line );
  138. }
  139. }
  140. }
  141. if( !line.empty() )
  142. lines.push_back( line );
  143. }
  144. // PRIVATE METHOD UNCHANGED FROM wxWidgets IMPLEMENTATION
  145. wxCoord GRIDCELL_AUTOWRAP_STRINGRENDERER::BreakWord( wxDC& dc, const wxString& word,
  146. wxCoord maxWidth, wxArrayString& lines,
  147. wxString& line )
  148. {
  149. wxArrayInt widths;
  150. dc.GetPartialTextExtents( word, widths );
  151. // TODO: Use binary search to find the first element > maxWidth.
  152. const unsigned count = widths.size();
  153. unsigned n;
  154. for( n = 0; n < count; n++ )
  155. {
  156. if( widths[n] > maxWidth )
  157. break;
  158. }
  159. if( n == 0 )
  160. {
  161. // This is a degenerate case: the first character of the word is
  162. // already wider than the available space, so we just can't show it
  163. // completely and have to put the first character in this line.
  164. n = 1;
  165. }
  166. lines.push_back( word.substr( 0, n ) );
  167. // Check if the remainder of the string fits in one line.
  168. //
  169. // Unfortunately we can't use the existing partial text extents as the
  170. // extent of the remainder may be different when it's rendered in a
  171. // separate line instead of as part of the same one, so we have to
  172. // recompute it.
  173. const wxString rest = word.substr( n );
  174. const wxCoord restWidth = dc.GetTextExtent( rest ).x;
  175. if( restWidth <= maxWidth )
  176. {
  177. line = rest;
  178. return restWidth;
  179. }
  180. // Break the rest of the word into lines.
  181. //
  182. // TODO: Perhaps avoid recursion? The code is simpler like this but using a
  183. // loop in this function would probably be more efficient.
  184. return BreakWord( dc, rest, maxWidth, lines, line );
  185. }
  186. #define GRID_CELL_MARGIN 4
  187. int GRIDCELL_AUTOWRAP_STRINGRENDERER::GetHeight( wxDC& aDC, wxGrid* aGrid, int aRow, int aCol )
  188. {
  189. wxGridCellAttr* attr = aGrid->GetOrCreateCellAttr( aRow, aCol );
  190. wxRect rect;
  191. aDC.SetFont( attr->GetFont() );
  192. rect.SetWidth( aGrid->GetColSize( aCol ) - ( 2 * GRID_CELL_MARGIN ) );
  193. const size_t numLines = GetTextLines( *aGrid, aDC, *attr, rect, aRow, aCol ).size();
  194. const int textHeight = numLines * aDC.GetCharHeight();
  195. attr->DecRef();
  196. return textHeight + ( 2 * GRID_CELL_MARGIN );
  197. }
  198. /**
  199. * A helper to handle symbols to edit.
  200. */
  201. class SYMBOL_CANDIDATE
  202. {
  203. public:
  204. SYMBOL_CANDIDATE( SCH_SYMBOL* aSymbol )
  205. {
  206. m_Symbol = aSymbol;
  207. m_InitialLibId = m_Symbol->GetLibId().Format();
  208. m_Row = -1;
  209. m_IsOrphan = false;
  210. m_Screen = nullptr;
  211. }
  212. // Return a string like mylib:symbol_name from the #LIB_ID of the symbol.
  213. wxString GetStringLibId()
  214. {
  215. return m_Symbol->GetLibId().GetUniStringLibId();
  216. }
  217. SCH_SYMBOL* m_Symbol; // the schematic symbol
  218. int m_Row; // the row index in m_grid
  219. SCH_SCREEN* m_Screen; // the screen where m_Symbol lives
  220. wxString m_Reference; // the schematic reference, only to display it in list
  221. wxString m_InitialLibId; // the Lib Id of the symbol before any change.
  222. bool m_IsOrphan; // true if a symbol has no corresponding symbol found in libs.
  223. };
  224. /**
  225. * Dialog to globally edit the #LIB_ID of groups if symbols having the same initial LIB_ID.
  226. *
  227. * This is useful when you want to:
  228. * * move a symbol from a symbol library to another symbol library.
  229. * * change the nickname of a library.
  230. * * globally replace the symbol used by a group of symbols by another symbol.
  231. */
  232. class DIALOG_EDIT_SYMBOLS_LIBID : public DIALOG_EDIT_SYMBOLS_LIBID_BASE
  233. {
  234. public:
  235. DIALOG_EDIT_SYMBOLS_LIBID( SCH_EDIT_FRAME* aParent );
  236. ~DIALOG_EDIT_SYMBOLS_LIBID() override;
  237. SCH_EDIT_FRAME* GetParent();
  238. bool IsSchematicModified() { return m_isModified; }
  239. private:
  240. void initDlg();
  241. /**
  242. * Add a new row (new entry) in m_grid.
  243. *
  244. * @param aMarkRow set to true to use bold/italic font in column COL_CURR_LIBID.
  245. * @param aReferences is the value of cell( aRowId, COL_REFS).
  246. * @param aStrLibId is the value of cell( aRowId, COL_CURR_LIBID).
  247. */
  248. void AddRowToGrid( bool aMarkRow, const wxString& aReferences, const wxString& aStrLibId );
  249. /// returns true if all new lib id are valid
  250. bool validateLibIds();
  251. /**
  252. * Run the lib browser and set the selected #LIB_ID for \a aRow.
  253. *
  254. * @param aRow is the row to edit.
  255. * @return false if the command was aborted.
  256. */
  257. bool setLibIdByBrowser( int aRow );
  258. // Event handlers
  259. // called on a right click or a left double click:
  260. void onCellBrowseLib( wxGridEvent& event ) override;
  261. // Cancel all changes, and close the dialog
  262. void onCancel( wxCommandEvent& event ) override
  263. {
  264. // Just skipping the event doesn't work after the library browser was run
  265. if( IsQuasiModal() )
  266. EndQuasiModal( wxID_CANCEL );
  267. else
  268. event.Skip();
  269. }
  270. // Try to find a candidate for non existing symbols
  271. void onClickOrphansButton( wxCommandEvent& event ) override;
  272. // Automatically called when click on OK button
  273. bool TransferDataFromWindow() override;
  274. void AdjustGridColumns( int aWidth );
  275. void OnSizeGrid( wxSizeEvent& event ) override;
  276. bool m_isModified; // set to true if the schematic is modified
  277. std::vector<int> m_OrphansRowIndexes; // list of rows containing orphan lib_id
  278. std::vector<SYMBOL_CANDIDATE> m_symbols;
  279. GRIDCELL_AUTOWRAP_STRINGRENDERER* m_autoWrapRenderer;
  280. };
  281. DIALOG_EDIT_SYMBOLS_LIBID::DIALOG_EDIT_SYMBOLS_LIBID( SCH_EDIT_FRAME* aParent )
  282. :DIALOG_EDIT_SYMBOLS_LIBID_BASE( aParent )
  283. {
  284. m_autoWrapRenderer = new GRIDCELL_AUTOWRAP_STRINGRENDERER;
  285. m_grid->PushEventHandler( new GRID_TRICKS( m_grid ) );
  286. initDlg();
  287. finishDialogSettings();
  288. }
  289. DIALOG_EDIT_SYMBOLS_LIBID::~DIALOG_EDIT_SYMBOLS_LIBID()
  290. {
  291. // Delete the GRID_TRICKS.
  292. m_grid->PopEventHandler( true );
  293. m_autoWrapRenderer->DecRef();
  294. }
  295. // A sort compare function to sort symbols list by LIB_ID and then reference.
  296. static bool sort_by_libid( const SYMBOL_CANDIDATE& candidate1, const SYMBOL_CANDIDATE& candidate2 )
  297. {
  298. if( candidate1.m_Symbol->GetLibId() == candidate2.m_Symbol->GetLibId() )
  299. return candidate1.m_Reference.Cmp( candidate2.m_Reference ) < 0;
  300. return candidate1.m_Symbol->GetLibId() < candidate2.m_Symbol->GetLibId();
  301. }
  302. void DIALOG_EDIT_SYMBOLS_LIBID::initDlg()
  303. {
  304. // Clear the FormBuilder rows
  305. m_grid->ClearRows();
  306. m_isModified = false;
  307. // This option build the full symbol list.
  308. // In complex hierarchies, the same symbol is in fact duplicated, but
  309. // it is listed with different references (one by sheet instance)
  310. // the list is larger and looks like it contains all symbols.
  311. const SCH_SHEET_LIST& sheets = GetParent()->Schematic().GetSheets();
  312. SCH_REFERENCE_LIST references;
  313. // build the full list of symbols including symbol having no symbol in loaded libs
  314. // (orphan symbols)
  315. sheets.GetSymbols( references, /* include power symbols */ true,
  316. /* include orphan symbols */ true );
  317. for( unsigned ii = 0; ii < references.GetCount(); ii++ )
  318. {
  319. SCH_REFERENCE& item = references[ii];
  320. SYMBOL_CANDIDATE candidate( item.GetSymbol() );
  321. candidate.m_Screen = item.GetSheetPath().LastScreen();
  322. SCH_SHEET_PATH sheetpath = item.GetSheetPath();
  323. candidate.m_Reference = candidate.m_Symbol->GetRef( &sheetpath );
  324. int unitcount = candidate.m_Symbol->GetUnitCount();
  325. candidate.m_IsOrphan = ( unitcount == 0 );
  326. m_symbols.push_back( candidate );
  327. }
  328. if( m_symbols.size() == 0 )
  329. return;
  330. // now sort by lib id to create groups of items having the same lib id
  331. std::sort( m_symbols.begin(), m_symbols.end(), sort_by_libid );
  332. // Now, fill m_grid
  333. wxString last_str_libid = m_symbols.front().GetStringLibId();
  334. int row = 0;
  335. wxString refs;
  336. wxString last_ref;
  337. bool mark_cell = m_symbols.front().m_IsOrphan;
  338. for( SYMBOL_CANDIDATE& symbol : m_symbols )
  339. {
  340. wxString str_libid = symbol.GetStringLibId();
  341. if( last_str_libid != str_libid )
  342. {
  343. // Add last group to grid
  344. AddRowToGrid( mark_cell, refs, last_str_libid );
  345. // prepare next entry
  346. mark_cell = symbol.m_IsOrphan;
  347. last_str_libid = str_libid;
  348. refs.Empty();
  349. row++;
  350. }
  351. else if( symbol.m_Reference == last_ref )
  352. {
  353. symbol.m_Row = row;
  354. continue;
  355. }
  356. last_ref = symbol.m_Reference;
  357. if( !refs.IsEmpty() )
  358. refs += wxT( ", " );
  359. refs += symbol.m_Reference;
  360. symbol.m_Row = row;
  361. }
  362. // Add last symbol group:
  363. AddRowToGrid( mark_cell, refs, last_str_libid );
  364. // Allows only the selection by row
  365. m_grid->SetSelectionMode( wxGrid::wxGridSelectRows );
  366. m_buttonOrphanItems->Enable( m_OrphansRowIndexes.size() > 0 );
  367. Layout();
  368. }
  369. SCH_EDIT_FRAME* DIALOG_EDIT_SYMBOLS_LIBID::GetParent()
  370. {
  371. return dynamic_cast<SCH_EDIT_FRAME*>( wxDialog::GetParent() );
  372. }
  373. void DIALOG_EDIT_SYMBOLS_LIBID::AddRowToGrid( bool aMarkRow, const wxString& aReferences,
  374. const wxString& aStrLibId )
  375. {
  376. int row = m_grid->GetNumberRows();
  377. if( aMarkRow ) // An orphaned symbol exists, set m_AsOrphanCmp as true.
  378. m_OrphansRowIndexes.push_back( row );
  379. m_grid->AppendRows( 1 );
  380. m_grid->SetCellValue( row, COL_REFS, UnescapeString( aReferences ) );
  381. m_grid->SetReadOnly( row, COL_REFS );
  382. m_grid->SetCellValue( row, COL_CURR_LIBID, UnescapeString( aStrLibId ) );
  383. m_grid->SetReadOnly( row, COL_CURR_LIBID );
  384. if( aMarkRow ) // A symbol is not existing in libraries: mark the cell
  385. {
  386. wxFont font = m_grid->GetDefaultCellFont();
  387. font.MakeBold();
  388. font.MakeItalic();
  389. m_grid->SetCellFont( row, COL_CURR_LIBID, font );
  390. }
  391. m_grid->SetCellRenderer( row, COL_REFS, m_autoWrapRenderer->Clone() );
  392. // wxWidgets' AutoRowHeight fails when used with wxGridCellAutoWrapStringRenderer
  393. // (fixed in 2014, but didn't get in to wxWidgets 3.0.2)
  394. wxClientDC dc( this );
  395. m_grid->SetRowSize( row, m_autoWrapRenderer->GetHeight( dc, m_grid, row, COL_REFS ) );
  396. // set new libid column browse button
  397. wxGridCellAttr* attr = new wxGridCellAttr;
  398. attr->SetEditor( new GRID_CELL_SYMBOL_ID_EDITOR( this, UnescapeString( aStrLibId ) ) );
  399. m_grid->SetAttr( row, COL_NEW_LIBID, attr );
  400. }
  401. wxString getLibIdValue( const WX_GRID* aGrid, int aRow, int aCol )
  402. {
  403. wxString rawValue = aGrid->GetCellValue( aRow, aCol );
  404. if( rawValue.IsEmpty() )
  405. return rawValue;
  406. wxString itemName;
  407. wxString libName = rawValue.BeforeFirst( ':', &itemName );
  408. return EscapeString( libName, CTX_LIBID ) + ':' + EscapeString( itemName, CTX_LIBID );
  409. }
  410. bool DIALOG_EDIT_SYMBOLS_LIBID::validateLibIds()
  411. {
  412. if( !m_grid->CommitPendingChanges() )
  413. return false;
  414. int row_max = m_grid->GetNumberRows() - 1;
  415. for( int row = 0; row <= row_max; row++ )
  416. {
  417. wxString new_libid = getLibIdValue( m_grid, row, COL_NEW_LIBID );
  418. if( new_libid.IsEmpty() )
  419. continue;
  420. // a new lib id is found. validate this new value
  421. LIB_ID id;
  422. id.Parse( new_libid );
  423. if( !id.IsValid() )
  424. {
  425. wxString msg;
  426. msg.Printf( _( "Symbol library identifier %s is not valid." ), new_libid );
  427. wxMessageBox( msg );
  428. m_grid->SetFocus();
  429. m_grid->MakeCellVisible( row, COL_NEW_LIBID );
  430. m_grid->SetGridCursor( row, COL_NEW_LIBID );
  431. m_grid->EnableCellEditControl( true );
  432. m_grid->ShowCellEditControl();
  433. return false;
  434. }
  435. }
  436. return true;
  437. }
  438. void DIALOG_EDIT_SYMBOLS_LIBID::onCellBrowseLib( wxGridEvent& event )
  439. {
  440. int row = event.GetRow();
  441. m_grid->SelectRow( row ); // only for user, to show the selected line
  442. setLibIdByBrowser( row );
  443. }
  444. void DIALOG_EDIT_SYMBOLS_LIBID::onClickOrphansButton( wxCommandEvent& event )
  445. {
  446. std::vector<wxString> libs = Prj().SchSymbolLibTable()->GetLogicalLibs();
  447. wxArrayString aliasNames;
  448. wxArrayString candidateSymbNames;
  449. unsigned fixesCount = 0;
  450. // Try to find a candidate for non existing symbols in any loaded library
  451. for( int orphanRow : m_OrphansRowIndexes )
  452. {
  453. wxString orphanLibid = getLibIdValue( m_grid, orphanRow, COL_CURR_LIBID );
  454. int grid_row_idx = orphanRow; //row index in m_grid for the current item
  455. LIB_ID curr_libid;
  456. curr_libid.Parse( orphanLibid, true );
  457. wxString symbolName = curr_libid.GetLibItemName();
  458. // number of full LIB_ID candidates (because we search for a symbol name
  459. // inside all available libraries, perhaps the same symbol name can be found
  460. // in more than one library, giving ambiguity
  461. int libIdCandidateCount = 0;
  462. candidateSymbNames.Clear();
  463. // now try to find a candidate
  464. for( const wxString &lib : libs )
  465. {
  466. aliasNames.Clear();
  467. try
  468. {
  469. Prj().SchSymbolLibTable()->EnumerateSymbolLib( lib, aliasNames );
  470. }
  471. catch( const IO_ERROR& ) {} // ignore, it is handled below
  472. if( aliasNames.IsEmpty() )
  473. continue;
  474. // Find a symbol name in symbols inside this library:
  475. int index = aliasNames.Index( symbolName );
  476. if( index != wxNOT_FOUND )
  477. {
  478. // a candidate is found!
  479. libIdCandidateCount++;
  480. wxString newLibid = lib + ':' + symbolName;
  481. // Uses the first found. Most of time, it is alone.
  482. // Others will be stored in a candidate list
  483. if( libIdCandidateCount <= 1 )
  484. {
  485. m_grid->SetCellValue( grid_row_idx, COL_NEW_LIBID, UnescapeString( newLibid ) );
  486. candidateSymbNames.Add( m_grid->GetCellValue( grid_row_idx, COL_NEW_LIBID ) );
  487. fixesCount++;
  488. }
  489. else // Store other candidates for later selection
  490. {
  491. candidateSymbNames.Add( UnescapeString( newLibid ) );
  492. }
  493. }
  494. }
  495. // If more than one LIB_ID candidate, ask for selection between candidates:
  496. if( libIdCandidateCount > 1 )
  497. {
  498. // Mainly for user: select the row being edited
  499. m_grid->SelectRow( grid_row_idx );
  500. wxString msg;
  501. msg.Printf( _( "Available Candidates for %s " ),
  502. m_grid->GetCellValue( grid_row_idx, COL_CURR_LIBID ) );
  503. wxSingleChoiceDialog dlg ( this, msg,
  504. wxString::Format( _( "Candidates count %d " ),
  505. libIdCandidateCount ),
  506. candidateSymbNames );
  507. if( dlg.ShowModal() == wxID_OK )
  508. m_grid->SetCellValue( grid_row_idx, COL_NEW_LIBID, dlg.GetStringSelection() );
  509. }
  510. }
  511. if( fixesCount < m_OrphansRowIndexes.size() ) // Not all orphan symbols are fixed.
  512. {
  513. wxMessageBox( wxString::Format( _( "%u link(s) mapped, %u not found" ),
  514. fixesCount,
  515. (unsigned) m_OrphansRowIndexes.size() - fixesCount ) );
  516. }
  517. else
  518. {
  519. wxMessageBox( wxString::Format( _( "All %u link(s) resolved" ), fixesCount ) );
  520. }
  521. }
  522. bool DIALOG_EDIT_SYMBOLS_LIBID::setLibIdByBrowser( int aRow )
  523. {
  524. #if 0
  525. // Use dialog symbol selector to choose a symbol
  526. SCH_BASE_FRAME::HISTORY_LIST dummy;
  527. SCH_BASE_FRAME::PICKED_SYMBOL sel = m_frame->SelectComponentFromLibrary( nullptr, dummy, true,
  528. 0, 0, false );
  529. #else
  530. // Use library viewer to choose a symbol
  531. LIB_ID preselected;
  532. wxString current = getLibIdValue( m_grid, aRow, COL_NEW_LIBID );
  533. if( current.IsEmpty() )
  534. current = getLibIdValue( m_grid, aRow, COL_CURR_LIBID );
  535. if( !current.IsEmpty() )
  536. preselected.Parse( current, true );
  537. PICKED_SYMBOL sel = GetParent()->PickSymbolFromLibBrowser( this, nullptr, preselected, 0, 0 );
  538. #endif
  539. if( sel.LibId.empty() ) // command aborted
  540. return false;
  541. if( !sel.LibId.IsValid() ) // Should not occur
  542. {
  543. wxMessageBox( _( "Invalid symbol library identifier" ) );
  544. return false;
  545. }
  546. wxString new_libid;
  547. new_libid = sel.LibId.Format();
  548. m_grid->SetCellValue( aRow, COL_NEW_LIBID, UnescapeString( new_libid ) );
  549. return true;
  550. }
  551. bool DIALOG_EDIT_SYMBOLS_LIBID::TransferDataFromWindow()
  552. {
  553. if( !validateLibIds() )
  554. return false;
  555. auto getName = []( const LIB_ID& aLibId )
  556. {
  557. return UnescapeString( aLibId.GetLibItemName().wx_str() );
  558. };
  559. int row_max = m_grid->GetNumberRows() - 1;
  560. for( int row = 0; row <= row_max; row++ )
  561. {
  562. wxString new_libid = getLibIdValue( m_grid, row, COL_NEW_LIBID );
  563. if( new_libid.IsEmpty() || new_libid == getLibIdValue( m_grid, row, COL_CURR_LIBID ) )
  564. continue;
  565. // A new lib id is found and was already validated.
  566. LIB_ID id;
  567. id.Parse( new_libid, true );
  568. for( SYMBOL_CANDIDATE& candidate : m_symbols )
  569. {
  570. if( candidate.m_Row != row )
  571. continue;
  572. LIB_SYMBOL* symbol = nullptr;
  573. try
  574. {
  575. symbol = Prj().SchSymbolLibTable()->LoadSymbol( id );
  576. }
  577. catch( const IO_ERROR& ioe )
  578. {
  579. wxString msg;
  580. msg.Printf( _( "Error loading symbol %s from library %s.\n\n%s" ),
  581. id.GetLibItemName().wx_str(),
  582. id.GetLibNickname().wx_str(),
  583. ioe.What() );
  584. DisplayError( this, msg );
  585. }
  586. if( symbol == nullptr )
  587. continue;
  588. GetParent()->SaveCopyInUndoList( candidate.m_Screen, candidate.m_Symbol,
  589. UNDO_REDO::CHANGED, m_isModified );
  590. m_isModified = true;
  591. candidate.m_Screen->Remove( candidate.m_Symbol );
  592. SCH_FIELD* value = candidate.m_Symbol->GetField( VALUE_FIELD );
  593. // If value is a proxy for the itemName then make sure it gets updated
  594. if( getName( candidate.m_Symbol->GetLibId() ) == value->GetText() )
  595. candidate.m_Symbol->SetValue( getName( id ) );
  596. candidate.m_Symbol->SetLibId( id );
  597. candidate.m_Symbol->SetLibSymbol( symbol->Flatten().release() );
  598. candidate.m_Screen->Append( candidate.m_Symbol );
  599. candidate.m_Screen->SetContentModified();
  600. if ( m_checkBoxUpdateFields->IsChecked() )
  601. {
  602. candidate.m_Symbol->UpdateFields( nullptr,
  603. false, /* update style */
  604. false, /* update ref */
  605. false, /* update other fields */
  606. false, /* reset ref */
  607. true /* reset other fields */ );
  608. }
  609. }
  610. }
  611. return true;
  612. }
  613. void DIALOG_EDIT_SYMBOLS_LIBID::AdjustGridColumns( int aWidth )
  614. {
  615. // Account for scroll bars
  616. aWidth -= ( m_grid->GetSize().x - m_grid->GetClientSize().x );
  617. int colWidth = aWidth / 3;
  618. m_grid->SetColSize( COL_REFS, colWidth );
  619. aWidth -= colWidth;
  620. colWidth = 0;
  621. for( int row = 0; row < m_grid->GetNumberRows(); ++row )
  622. {
  623. wxString cellValue = m_grid->GetCellValue( row, COL_CURR_LIBID );
  624. colWidth = std::max( colWidth, KIUI::GetTextSize( cellValue, m_grid ).x );
  625. }
  626. colWidth += 20;
  627. m_grid->SetColSize( COL_CURR_LIBID, colWidth );
  628. aWidth -= colWidth;
  629. colWidth = 0;
  630. for( int row = 0; row < m_grid->GetNumberRows(); ++row )
  631. {
  632. wxString cellValue = m_grid->GetCellValue( row, COL_NEW_LIBID );
  633. colWidth = std::max( colWidth, KIUI::GetTextSize( cellValue, m_grid ).x );
  634. }
  635. colWidth += 20;
  636. m_grid->SetColSize( COL_NEW_LIBID, std::max( colWidth, aWidth ) );
  637. }
  638. void DIALOG_EDIT_SYMBOLS_LIBID::OnSizeGrid( wxSizeEvent& event )
  639. {
  640. AdjustGridColumns( event.GetSize().GetX() );
  641. wxClientDC dc( this );
  642. // wxWidgets' AutoRowHeight fails when used with wxGridCellAutoWrapStringRenderer
  643. for( int row = 0; row < m_grid->GetNumberRows(); ++row )
  644. m_grid->SetRowSize( row, m_autoWrapRenderer->GetHeight( dc, m_grid, row, COL_REFS ) );
  645. event.Skip();
  646. }
  647. bool InvokeDialogEditSymbolsLibId( SCH_EDIT_FRAME* aCaller )
  648. {
  649. // This dialog itself subsequently can invoke a KIWAY_PLAYER as a quasimodal
  650. // frame. Therefore this dialog as a modal frame parent, MUST be run under
  651. // quasimodal mode for the quasimodal frame support to work. So don't use
  652. // the QUASIMODAL macros here.
  653. DIALOG_EDIT_SYMBOLS_LIBID dlg( aCaller );
  654. // DO NOT use ShowModal() here, otherwise the library browser will not work properly.
  655. dlg.ShowQuasiModal();
  656. return dlg.IsSchematicModified();
  657. }