kicad/eeschema/dialogs/dialog_edit_components_libi...

832 lines
26 KiB
C++
Raw Normal View History

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright 2017 Jean-Pierre Charras, jp.charras@wanadoo.fr
Make the new schematic and symbol library file formats the default. This is a very large and potentially disruptive change so this will be an unusually long and detailed commit message. The new file formats are now the default in both the schematic and symbol library editors. Existing symbol libraries will be saved in their current format until new features are added to library symbols. Once this happens, both the legacy schematic and symbol file formats will be no longer be savable and existing libraries will have to be converted. Saving to the legacy file formats is still available for round robin testing and should not be used for normal editing. When loading the legacy schematic file, it is imperative that the schematic library symbols are rescued and/or remapped to valid library identifiers. Otherwise, there will be no way to link to the original library symbol and the user will be required manually set the library identifier. The cached symbol will be saved in the schematic file so the last library symbol in the cache will still be used but there will be no way to update it from the original library. The next save after loading a legacy schematic file will be converted to the s-expression file format. Schematics with hierarchical sheets will automatically have all sheet file name extensions changed to .kicad_sym and saved to the new format as well. Appending schematics requires that the schematic to append has already been converted to the new file format. This is required to ensure that library symbols are guaranteed to be valid for the appended schematic. The schematic symbol library symbol link resolution has been moved out of the SCH_COMPONENT object and move into the SCH_SCREEN object that owns the symbol. This was done to ensure that there is a single place where the library symbol links get resolved rather than the dozen or so different code paths that previously existed. It also removes the necessity of the SCH_COMPONENT object of requiring any knowledge of the symbol library table and/or the cache library. When opening an s-expression schematic, the legacy cache library is not loaded so any library symbols not rescued cannot be loaded. Broken library symbol links will have to be manually resolved by adding the cache library to the symbol library table and changing the links in the schematic symbol. Now that the library symbols are embedded in the schematic file, the SCH_SCREEN object maintains the list of library symbols for the schematic automatically. No external manipulation of this library cache should ever occur. ADDED: S-expression schematic and symbol library file formats.
2020-04-16 16:43:50 +00:00
* Copyright 1992-2020 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
* or you may search the http://www.gnu.org website for the version 2 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
/**
* @file eeschema/dialogs/dialog_edit_components_libid.cpp
2018-04-08 10:28:59 +00:00
* @brief Dialog to remap library id of components to another library id
*/
#include <fctsys.h>
#include <confirm.h>
2018-01-30 10:49:51 +00:00
#include <sch_edit_frame.h>
#include <sch_draw_panel.h>
#include <sch_component.h>
#include <sch_reference_list.h>
#include <schematic.h>
#include <pgm_base.h>
#include <symbol_lib_table.h>
#include <trace_helpers.h>
#include <widgets/wx_grid.h>
#include <dialog_edit_components_libid_base.h>
#include <wx/tokenzr.h>
#include <grid_tricks.h>
#include <widgets/grid_text_button_helpers.h>
#define COL_REFS 0
#define COL_CURR_LIBID 1
#define COL_NEW_LIBID 2
// a re-implementation of wxGridCellAutoWrapStringRenderer to allow workaround to autorowsize bug
class GRIDCELL_AUTOWRAP_STRINGRENDERER : public wxGridCellAutoWrapStringRenderer
{
public:
int GetHeight( wxDC& aDC, wxGrid* aGrid, int aRow, int aCol );
wxGridCellRenderer *Clone() const override
{ return new GRIDCELL_AUTOWRAP_STRINGRENDERER; }
private:
// HELPER ROUTINES UNCHANGED FROM wxWidgets IMPLEMENTATION
wxArrayString GetTextLines( wxGrid& grid,
wxDC& dc,
const wxGridCellAttr& attr,
const wxRect& rect,
int row, int col);
// Helper methods of GetTextLines()
// Break a single logical line of text into several physical lines, all of
// which are added to the lines array. The lines are broken at maxWidth and
// the dc is used for measuring text extent only.
void BreakLine(wxDC& dc,
const wxString& logicalLine,
wxCoord maxWidth,
wxArrayString& lines);
// Break a word, which is supposed to be wider than maxWidth, into several
// lines, which are added to lines array and the last, incomplete, of which
// is returned in line output parameter.
//
// Returns the width of the last line.
wxCoord BreakWord(wxDC& dc,
const wxString& word,
wxCoord maxWidth,
wxArrayString& lines,
wxString& line);
};
// PRIVATE METHOD UNCHANGED FROM wxWidgets IMPLEMENTATION
wxArrayString
GRIDCELL_AUTOWRAP_STRINGRENDERER::GetTextLines(wxGrid& grid,
wxDC& dc,
const wxGridCellAttr& attr,
const wxRect& rect,
int row, int col)
{
dc.SetFont(attr.GetFont());
const wxCoord maxWidth = rect.GetWidth();
// Transform logical lines into physical ones, wrapping the longer ones.
const wxArrayString
logicalLines = wxSplit(grid.GetCellValue(row, col), '\n', '\0');
// Trying to do anything if the column is hidden anyhow doesn't make sense
// and we run into problems in BreakLine() in this case.
if ( maxWidth <= 0 )
return logicalLines;
wxArrayString physicalLines;
for ( wxArrayString::const_iterator it = logicalLines.begin();
it != logicalLines.end();
++it )
{
const wxString& line = *it;
if ( dc.GetTextExtent(line).x > maxWidth )
{
// Line does not fit, break it up.
BreakLine(dc, line, maxWidth, physicalLines);
}
else // The entire line fits as is
{
physicalLines.push_back(line);
}
}
return physicalLines;
}
// PRIVATE METHOD UNCHANGED FROM wxWidgets IMPLEMENTATION
void
GRIDCELL_AUTOWRAP_STRINGRENDERER::BreakLine(wxDC& dc,
const wxString& logicalLine,
wxCoord maxWidth,
wxArrayString& lines)
{
wxCoord lineWidth = 0;
wxString line;
// For each word
wxStringTokenizer wordTokenizer(logicalLine, wxS(" \t"), wxTOKEN_RET_DELIMS);
while ( wordTokenizer.HasMoreTokens() )
{
const wxString word = wordTokenizer.GetNextToken();
const wxCoord wordWidth = dc.GetTextExtent(word).x;
if ( lineWidth + wordWidth < maxWidth )
{
// Word fits, just add it to this line.
line += word;
lineWidth += wordWidth;
}
else
{
// Word does not fit, check whether the word is itself wider that
// available width
if ( wordWidth < maxWidth )
{
// Word can fit in a new line, put it at the beginning
// of the new line.
lines.push_back(line);
line = word;
lineWidth = wordWidth;
}
else // Word cannot fit in available width at all.
{
if ( !line.empty() )
{
lines.push_back(line);
line.clear();
lineWidth = 0;
}
// Break it up in several lines.
lineWidth = BreakWord(dc, word, maxWidth, lines, line);
}
}
}
if ( !line.empty() )
lines.push_back(line);
}
// PRIVATE METHOD UNCHANGED FROM wxWidgets IMPLEMENTATION
wxCoord
GRIDCELL_AUTOWRAP_STRINGRENDERER::BreakWord(wxDC& dc,
const wxString& word,
wxCoord maxWidth,
wxArrayString& lines,
wxString& line)
{
wxArrayInt widths;
dc.GetPartialTextExtents(word, widths);
// TODO: Use binary search to find the first element > maxWidth.
const unsigned count = widths.size();
unsigned n;
for ( n = 0; n < count; n++ )
{
if ( widths[n] > maxWidth )
break;
}
if ( n == 0 )
{
// This is a degenerate case: the first character of the word is
// already wider than the available space, so we just can't show it
// completely and have to put the first character in this line.
n = 1;
}
lines.push_back(word.substr(0, n));
// Check if the remainder of the string fits in one line.
//
// Unfortunately we can't use the existing partial text extents as the
// extent of the remainder may be different when it's rendered in a
// separate line instead of as part of the same one, so we have to
// recompute it.
const wxString rest = word.substr(n);
const wxCoord restWidth = dc.GetTextExtent(rest).x;
if ( restWidth <= maxWidth )
{
line = rest;
return restWidth;
}
// Break the rest of the word into lines.
//
// TODO: Perhaps avoid recursion? The code is simpler like this but using a
// loop in this function would probably be more efficient.
return BreakWord(dc, rest, maxWidth, lines, line);
}
#define GRID_CELL_MARGIN 4
int GRIDCELL_AUTOWRAP_STRINGRENDERER::GetHeight( wxDC& aDC, wxGrid* aGrid, int aRow, int aCol )
{
wxGridCellAttr* attr = aGrid->GetOrCreateCellAttr( aRow, aCol );
wxRect rect;
aDC.SetFont( attr->GetFont() );
rect.SetWidth( aGrid->GetColSize( aCol ) - ( 2 * GRID_CELL_MARGIN ) );
const size_t numLines = GetTextLines( *aGrid, aDC, *attr, rect, aRow, aCol ).size();
const int textHeight = numLines * aDC.GetCharHeight();
attr->DecRef();
return textHeight + ( 2 * GRID_CELL_MARGIN );
}
// a helper class to handle components to edit
class CMP_CANDIDATE
{
public:
SCH_COMPONENT* m_Component; // the schematic component
int m_Row; // the row index in m_grid
SCH_SCREEN* m_Screen; // the screen where m_Component lives
wxString m_Reference; // the schematic reference, only to display it in list
wxString m_InitialLibId; // the Lib Id of the component before any change
bool m_IsOrphan; // true if a component has no corresponding symbol found in libs
CMP_CANDIDATE( SCH_COMPONENT* aComponent )
{
m_Component = aComponent;
m_InitialLibId = m_Component->GetLibId().Format();
m_Row = -1;
m_IsOrphan = false;
2017-12-04 09:20:05 +00:00
m_Screen = nullptr;
}
// Returns a string like mylib:symbol_name from the LIB_ID of the component
wxString GetStringLibId()
{
return m_Component->GetLibId().GetUniStringLibId();
}
// Returns a string containing the reference of the component
wxString GetSchematicReference()
{
return m_Reference;
}
};
/**
* Dialog to globally edit the #LIB_ID of groups if components having the same initial LIB_ID.
*
* This is useful when you want to:
* * to move a symbol from a symbol library to another symbol library
* to change the nickname of a library
* * globally replace the symbol used by a group of components by another symbol.
*/
class DIALOG_EDIT_COMPONENTS_LIBID : public DIALOG_EDIT_COMPONENTS_LIBID_BASE
{
public:
DIALOG_EDIT_COMPONENTS_LIBID( SCH_EDIT_FRAME* aParent );
~DIALOG_EDIT_COMPONENTS_LIBID() override;
SCH_EDIT_FRAME* GetParent();
bool IsSchematicModified() { return m_isModified; }
private:
bool m_isModified; // set to true if the schematic is modified
std::vector<int> m_OrphansRowIndexes; // list of rows containing orphan lib_id
std::vector<CMP_CANDIDATE> m_components;
GRIDCELL_AUTOWRAP_STRINGRENDERER* m_autoWrapRenderer;
void initDlg();
/**
* Add a new row (new entry) in m_grid.
* @param aMarkRow = true to use bold/italic font in column COL_CURR_LIBID
* @param aReferences is the value of cell( aRowId, COL_REFS)
* @param aStrLibId is the value of cell( aRowId, COL_CURR_LIBID)
*/
void AddRowToGrid( bool aMarkRow, const wxString& aReferences, const wxString& aStrLibId );
/// returns true if all new lib id are valid
bool validateLibIds();
/** run the lib browser and set the selected LIB_ID for row aRow
* @param aRow is the row to edit
* @return false if the command was aborted
*/
bool setLibIdByBrowser( int aRow );
// Events handlers
// called on a right click or a left double click:
void onCellBrowseLib( wxGridEvent& event ) override;
// Cancel all changes, and close the dialog
void onCancel( wxCommandEvent& event ) override
{
// Just skipping the event doesn't work after the library browser was run
if( IsQuasiModal() )
EndQuasiModal( wxID_CANCEL );
else
event.Skip();
}
// Try to find a candidate for non existing symbols
void onClickOrphansButton( wxCommandEvent& event ) override;
// Automatically called when click on OK button
bool TransferDataFromWindow() override;
void AdjustGridColumns( int aWidth );
void OnSizeGrid( wxSizeEvent& event ) override;
};
DIALOG_EDIT_COMPONENTS_LIBID::DIALOG_EDIT_COMPONENTS_LIBID( SCH_EDIT_FRAME* aParent )
:DIALOG_EDIT_COMPONENTS_LIBID_BASE( aParent )
{
m_autoWrapRenderer = new GRIDCELL_AUTOWRAP_STRINGRENDERER;
m_grid->PushEventHandler( new GRID_TRICKS( m_grid ) );
initDlg();
FinishDialogSettings();
}
DIALOG_EDIT_COMPONENTS_LIBID::~DIALOG_EDIT_COMPONENTS_LIBID()
{
// Delete the GRID_TRICKS.
m_grid->PopEventHandler( true );
m_autoWrapRenderer->DecRef();
}
// A sort compare function to sort components list by LIB_ID and then reference
static bool sort_by_libid( const CMP_CANDIDATE& cmp1, const CMP_CANDIDATE& cmp2 )
{
if( cmp1.m_Component->GetLibId() == cmp2.m_Component->GetLibId() )
return cmp1.m_Reference.Cmp( cmp2.m_Reference ) < 0;
return cmp1.m_Component->GetLibId() < cmp2.m_Component->GetLibId();
}
void DIALOG_EDIT_COMPONENTS_LIBID::initDlg()
{
// Clear the FormBuilder rows
m_grid->DeleteRows( 0, m_grid->GetNumberRows() );
m_isModified = false;
// This option build the full component list
// In complex hierarchies, the same component is in fact duplicated, but
// it is listed with different references (one by sheet instance)
// the list is larger and looks like it contains all components
const SCH_SHEET_LIST& sheets = GetParent()->Schematic().GetSheets();
SCH_REFERENCE_LIST references;
// build the full list of components including component having no symbol in loaded libs
// (orphan components)
sheets.GetComponents( references, /* include power symbols */true,
/* include orphan components */true );
for( unsigned ii = 0; ii < references.GetCount(); ii++ )
{
SCH_REFERENCE& item = references[ii];
CMP_CANDIDATE candidate( item.GetComp() );
candidate.m_Screen = item.GetSheetPath().LastScreen();
SCH_SHEET_PATH sheetpath = item.GetSheetPath();
candidate.m_Reference = candidate.m_Component->GetRef( &sheetpath );
int unitcount = candidate.m_Component->GetUnitCount();
candidate.m_IsOrphan = ( unitcount == 0 );
m_components.push_back( candidate );
}
if( m_components.size() == 0 )
return;
// now sort by lib id to create groups of items having the same lib id
std::sort( m_components.begin(), m_components.end(), sort_by_libid );
// Now, fill m_grid
wxString last_str_libid = m_components.front().GetStringLibId();
int row = 0;
wxString refs;
wxString last_ref;
bool mark_cell = m_components.front().m_IsOrphan;
for( auto& cmp : m_components )
{
wxString str_libid = cmp.GetStringLibId();
if( last_str_libid != str_libid )
{
// Add last group to grid
AddRowToGrid( mark_cell, refs, last_str_libid );
// prepare next entry
mark_cell = cmp.m_IsOrphan;
last_str_libid = str_libid;
refs.Empty();
row++;
}
else if( cmp.GetSchematicReference() == last_ref )
{
cmp.m_Row = row;
continue;
}
last_ref = cmp.GetSchematicReference();
if( !refs.IsEmpty() )
refs += wxT( ", " );
refs += cmp.GetSchematicReference();
cmp.m_Row = row;
}
// Add last component group:
AddRowToGrid( mark_cell, refs, last_str_libid );
// Allows only the selection by row
m_grid->SetSelectionMode( wxGrid::wxGridSelectRows );
m_buttonOrphanItems->Enable( m_OrphansRowIndexes.size() > 0 );
Layout();
}
SCH_EDIT_FRAME* DIALOG_EDIT_COMPONENTS_LIBID::GetParent()
{
return dynamic_cast<SCH_EDIT_FRAME*>( wxDialog::GetParent() );
}
void DIALOG_EDIT_COMPONENTS_LIBID::AddRowToGrid( bool aMarkRow, const wxString& aReferences,
const wxString& aStrLibId )
{
int row = m_grid->GetNumberRows();
if( aMarkRow ) // a orphan component exists, set m_AsOrphanCmp as true
m_OrphansRowIndexes.push_back( row );
m_grid->AppendRows( 1 );
m_grid->SetCellValue( row, COL_REFS, aReferences );
m_grid->SetReadOnly( row, COL_REFS );
m_grid->SetCellValue( row, COL_CURR_LIBID, aStrLibId );
m_grid->SetReadOnly( row, COL_CURR_LIBID );
if( aMarkRow ) // A symbol is not existing in libraries: mark the cell
{
wxFont font = m_grid->GetDefaultCellFont();
font.MakeBold();
font.MakeItalic();
m_grid->SetCellFont( row, COL_CURR_LIBID, font );
}
m_grid->SetCellRenderer( row, COL_REFS, m_autoWrapRenderer->Clone() );
// wxWidgets' AutoRowHeight fails when used with wxGridCellAutoWrapStringRenderer
// (fixed in 2014, but didn't get in to wxWidgets 3.0.2)
wxClientDC dc( this );
m_grid->SetRowSize( row, m_autoWrapRenderer->GetHeight( dc, m_grid, row, COL_REFS ) );
// set new libid column browse button
wxGridCellAttr* attr = new wxGridCellAttr;
attr->SetEditor( new GRID_CELL_SYMBOL_ID_EDITOR( this, aStrLibId ) );
m_grid->SetAttr( row, COL_NEW_LIBID, attr );
}
bool DIALOG_EDIT_COMPONENTS_LIBID::validateLibIds()
{
if( !m_grid->CommitPendingChanges() )
return false;
int row_max = m_grid->GetNumberRows() - 1;
for( int row = 0; row <= row_max; row++ )
{
wxString new_libid = m_grid->GetCellValue( row, COL_NEW_LIBID );
if( new_libid.IsEmpty() )
continue;
// a new lib id is found. validate this new value
LIB_ID id;
id.Parse( new_libid, LIB_ID::ID_SCH );
if( !id.IsValid() )
{
wxString msg;
msg.Printf( _( "Symbol library identifier \"%s\" is not valid." ), new_libid );
wxMessageBox( msg );
m_grid->SetFocus();
m_grid->MakeCellVisible( row, COL_NEW_LIBID );
m_grid->SetGridCursor( row, COL_NEW_LIBID );
m_grid->EnableCellEditControl( true );
m_grid->ShowCellEditControl();
return false;
}
}
return true;
}
void DIALOG_EDIT_COMPONENTS_LIBID::onCellBrowseLib( wxGridEvent& event )
{
int row = event.GetRow();
m_grid->SelectRow( row ); // only for user, to show the selected line
setLibIdByBrowser( row );
}
void DIALOG_EDIT_COMPONENTS_LIBID::onClickOrphansButton( wxCommandEvent& event )
{
std::vector< wxString > libs = Prj().SchSymbolLibTable()->GetLogicalLibs();
wxArrayString aliasNames;
wxArrayString candidateSymbNames;
unsigned fixesCount = 0;
// Try to find a candidate for non existing symbols in any loaded library
2020-07-13 13:16:21 +00:00
for( int orphanRow : m_OrphansRowIndexes )
{
2020-07-13 13:16:21 +00:00
wxString orphanLibid = m_grid->GetCellValue( orphanRow, COL_CURR_LIBID );
int grid_row_idx = orphanRow; //row index in m_grid for the current item
LIB_ID curr_libid;
curr_libid.Parse( orphanLibid, LIB_ID::ID_SCH, true );
wxString symbName = curr_libid.GetLibItemName();
// number of full LIB_ID candidates (because we search for a symbol name
// inside all avaiable libraries, perhaps the same symbol name can be found
// in more than one library, giving ambiguity
int libIdCandidateCount = 0;
candidateSymbNames.Clear();
// now try to find a candidate
for( auto &lib : libs )
{
aliasNames.Clear();
try
{
Prj().SchSymbolLibTable()->EnumerateSymbolLib( lib, aliasNames );
}
catch( const IO_ERROR& ) {} // ignore, it is handled below
if( aliasNames.IsEmpty() )
continue;
// Find a symbol name in symbols inside this library:
int index = aliasNames.Index( symbName );
if( index != wxNOT_FOUND )
{
// a candidate is found!
libIdCandidateCount++;
wxString newLibid = lib + ':' + symbName;
// Uses the first found. Most of time, it is alone.
// Others will be stored in a candidate list
if( libIdCandidateCount <= 1 )
{
m_grid->SetCellValue( grid_row_idx, COL_NEW_LIBID, newLibid );
candidateSymbNames.Add( m_grid->GetCellValue( grid_row_idx, COL_NEW_LIBID ) );
fixesCount++;
}
else // Store other candidates for later selection
{
candidateSymbNames.Add( newLibid );
}
}
}
// If more than one LIB_ID candidate, ask for selection between candidates:
if( libIdCandidateCount > 1 )
{
// Mainly for user: select the row being edited
m_grid->SelectRow( grid_row_idx );
wxString msg;
msg.Printf( _( "Available Candidates for %s " ),
m_grid->GetCellValue( grid_row_idx, COL_CURR_LIBID ) );
wxSingleChoiceDialog dlg ( this, msg,
wxString::Format( _( "Candidates count %d " ), libIdCandidateCount ),
candidateSymbNames );
if( dlg.ShowModal() == wxID_OK )
m_grid->SetCellValue( grid_row_idx, COL_NEW_LIBID, dlg.GetStringSelection() );
}
}
if( fixesCount < m_OrphansRowIndexes.size() ) // Not all orphan components are fixed
{
2018-12-11 17:56:20 +00:00
wxMessageBox( wxString::Format( _( "%u link(s) mapped, %u not found" ),
fixesCount,
(unsigned) m_OrphansRowIndexes.size() - fixesCount ) );
}
else
wxMessageBox( wxString::Format( _( "All %u link(s) resolved" ), fixesCount ) );
}
bool DIALOG_EDIT_COMPONENTS_LIBID::setLibIdByBrowser( int aRow )
{
#if 0
// Use dialog symbol selector to choose a symbol
SCH_BASE_FRAME::HISTORY_LIST dummy;
SCH_BASE_FRAME::COMPONENT_SELECTION sel =
m_frame->SelectComponentFromLibrary( NULL, dummy, true, 0, 0, false );
#else
// Use library viewer to choose a symbol
LIB_ID aPreselectedLibid;
wxString current = m_grid->GetCellValue( aRow, COL_NEW_LIBID );
if( current.IsEmpty() )
current = m_grid->GetCellValue( aRow, COL_CURR_LIBID );
if( !current.IsEmpty() )
aPreselectedLibid.Parse( current, LIB_ID::ID_SCH, true );
COMPONENT_SELECTION sel =
GetParent()->SelectComponentFromLibBrowser( this, NULL, aPreselectedLibid, 0, 0 );
#endif
if( sel.LibId.empty() ) // command aborted
return false;
if( !sel.LibId.IsValid() ) // Should not occur
{
wxMessageBox( _( "Invalid symbol library identifier" ) );
return false;
}
wxString new_libid;
new_libid = sel.LibId.Format();
m_grid->SetCellValue( aRow, COL_NEW_LIBID, new_libid );
return true;
}
bool DIALOG_EDIT_COMPONENTS_LIBID::TransferDataFromWindow()
{
if( !validateLibIds() )
return false;
int row_max = m_grid->GetNumberRows() - 1;
for( int row = 0; row <= row_max; row++ )
{
wxString new_libid = m_grid->GetCellValue( row, COL_NEW_LIBID );
if( new_libid.IsEmpty() || new_libid == m_grid->GetCellValue( row, COL_CURR_LIBID ) )
continue;
// A new lib id is found and was already validated.
LIB_ID id;
id.Parse( new_libid, LIB_ID::ID_SCH, true );
for( CMP_CANDIDATE& cmp : m_components )
{
if( cmp.m_Row != row )
continue;
LIB_PART* symbol = nullptr;
try
{
symbol = Prj().SchSymbolLibTable()->LoadSymbol( id );
}
catch( const IO_ERROR& ioe )
{
wxString msg;
2020-07-13 13:16:21 +00:00
msg.Printf( _( "Error occurred loading symbol %s from library %s.\n\n%s" ),
id.GetLibItemName().wx_str(),
id.GetLibNickname().wx_str(),
ioe.What() );
DisplayError( this, msg );
}
if( symbol == nullptr )
continue;
2020-08-26 18:04:32 +00:00
GetParent()->SaveCopyInUndoList( cmp.m_Screen, cmp.m_Component, UNDO_REDO::CHANGED,
2020-07-13 13:16:21 +00:00
m_isModified );
m_isModified = true;
cmp.m_Screen->Remove( cmp.m_Component );
SCH_FIELD* value = cmp.m_Component->GetField( VALUE );
// If value is a proxy for the itemName then make sure it gets updated
if( cmp.m_Component->GetLibId().GetLibItemName().wx_str() == value->GetText() )
value->SetText( id.GetLibItemName().wx_str() );
cmp.m_Component->SetLibId( id );
cmp.m_Component->SetLibSymbol( symbol->Flatten().release() );
cmp.m_Screen->Append( cmp.m_Component );
cmp.m_Screen->SetModify();
}
}
return true;
}
void DIALOG_EDIT_COMPONENTS_LIBID::AdjustGridColumns( int aWidth )
{
// Account for scroll bars
aWidth -= ( m_grid->GetSize().x - m_grid->GetClientSize().x );
int colWidth = aWidth / 3;
m_grid->SetColSize( COL_REFS, colWidth );
aWidth -= colWidth;
colWidth = 0;
for( int row = 0; row < m_grid->GetNumberRows(); ++row )
{
wxString cellValue = m_grid->GetCellValue( row, COL_CURR_LIBID );
colWidth = std::max( colWidth, GetTextSize( cellValue, m_grid ).x );
}
colWidth += 20;
m_grid->SetColSize( COL_CURR_LIBID, colWidth );
aWidth -= colWidth;
colWidth = 0;
for( int row = 0; row < m_grid->GetNumberRows(); ++row )
{
wxString cellValue = m_grid->GetCellValue( row, COL_NEW_LIBID );
colWidth = std::max( colWidth, GetTextSize( cellValue, m_grid ).x );
}
colWidth += 20;
m_grid->SetColSize( COL_NEW_LIBID, std::max( colWidth, aWidth ) );
}
void DIALOG_EDIT_COMPONENTS_LIBID::OnSizeGrid( wxSizeEvent& event )
{
AdjustGridColumns( event.GetSize().GetX() );
wxClientDC dc( this );
// wxWidgets' AutoRowHeight fails when used with wxGridCellAutoWrapStringRenderer
for( int row = 0; row < m_grid->GetNumberRows(); ++row )
m_grid->SetRowSize( row, m_autoWrapRenderer->GetHeight( dc, m_grid, row, COL_REFS ) );
event.Skip();
}
bool InvokeDialogEditComponentsLibId( SCH_EDIT_FRAME* aCaller )
{
// This dialog itself subsequently can invoke a KIWAY_PLAYER as a quasimodal
// frame. Therefore this dialog as a modal frame parent, MUST be run under
// quasimodal mode for the quasimodal frame support to work. So don't use
// the QUASIMODAL macros here.
DIALOG_EDIT_COMPONENTS_LIBID dlg( aCaller );
// DO NOT use ShowModal() here, otherwise the library browser will not work
// properly.
dlg.ShowQuasiModal();
return dlg.IsSchematicModified();
}