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.

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