kicad/eeschema/dialogs/dialog_edit_components_libi...

628 lines
20 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright 2017 Jean-Pierre Charras, jp.charras@wanadoo.fr
* Copyright 1992-2018 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
* @brief Dialog to remap library id of components to another library id
*/
#include <fctsys.h>
#include <dialog_edit_components_libid_base.h>
#include <sch_edit_frame.h>
#include <class_drawpanel.h>
#include <sch_component.h>
#include <sch_reference_list.h>
#include <pgm_base.h>
#include <symbol_lib_table.h>
#include <wx/choicdlg.h>
#define COL_REFS 0
#define COL_CURR_LIBID 1
#define COL_NEW_LIBID 2
// 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;
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_EDIT_COMPONENTS_LIBID is a dialog to globally edit the LIB_ID of groups if components
* having the same initial LIB_ID.
* this is useful when you want:
* 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 );
bool IsSchematicModified() { return m_isModified; }
private:
SCH_EDIT_FRAME* m_parent;
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;
void initDlg();
/**
* Add a new row (new entry) in m_grid.
* @param aRowId is the row index
* @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( int aRowId, bool aMarkRow,
const wxString& aReferences, const wxString& aStrLibId );
/// returns true if all new lib id are valid
bool validateLibIds();
/// Reverts all changes already made
void revertChanges();
/** 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;
// Apply changes, but do not close the dialog
void onApplyButton( wxCommandEvent& event ) override;
// Cancel all changes, and close the dialog
void onCancel( wxCommandEvent& event ) override
{
if( m_isModified )
revertChanges();
event.Skip();
}
void onButtonBrowseLibraries( wxCommandEvent& event ) override;
// Undo all changes, and clear the list of new lib_ids
void onUndoChangesButton( wxCommandEvent& event ) override;
// Try to find a candidate for non existing symbols
void onClickOrphansButton( wxCommandEvent& event ) override;
// UI event, to enable/disable buttons
void updateUIChangesButton( wxUpdateUIEvent& event ) override
{
m_buttonUndo->Enable( m_isModified );
}
void updateUIBrowseButton( wxUpdateUIEvent& event ) override
{
wxArrayInt rows = m_grid->GetSelectedRows();
m_buttonBrowseLibs->Enable( rows.GetCount() == 1 );
}
// Automatically called when click on OK button
bool TransferDataFromWindow() override;
};
DIALOG_EDIT_COMPONENTS_LIBID::DIALOG_EDIT_COMPONENTS_LIBID( SCH_EDIT_FRAME* aParent )
:DIALOG_EDIT_COMPONENTS_LIBID_BASE( aParent )
{
m_parent = aParent;
initDlg();
// Gives a min size to display m_grid, now it is populated:
int minwidth = 30 // a margin
+ m_grid->GetRowLabelSize() + m_grid->GetColSize( COL_REFS )
+ m_grid->GetColSize( COL_CURR_LIBID ) + m_grid->GetColSize( COL_NEW_LIBID );
SetMinClientSize( wxSize( minwidth, VertPixelsFromDU( 250 ) ) );
SetSizeInDU( 500, 400 );
Center();
}
// A sort compare function to sort components list by LIB_ID
// inside the group of same LIB_ID, sort by 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()
{
m_isModified = false;
// Build the component list:
#if 0
// This option build a component list that works fine to edit LIB_ID fields, but does not display
// all components in a complex hierarchy.
// the list is shorter, but can be look like there are missing components in list
SCH_SCREENS screens;
for( SCH_SCREEN* screen = screens.GetFirst(); screen; screen = screens.GetNext() )
{
for( SCH_ITEM* item = screen->GetDrawItems(); item; item = item->Next() )
{
if( item->Type() == SCH_COMPONENT_T )
{
CMP_CANDIDATE candidate( static_cast< SCH_COMPONENT* >( item ) );
candidate.m_Screen = screen;
candidate.m_Reference = candidate.m_Component->GetField( REFERENCE )->GetFullyQualifiedText();
m_components.push_back( candidate );
}
}
}
#else
// 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
SCH_SHEET_LIST sheets( g_RootSheet );
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 );
// For multi units per package , add unit id.
// however, there is a problem: the unit id stored is always >= 1
// and 1 for no multi units.
// so add unit id only if unit > 1 if the unit count is > 1
// (can be 0 if the symbol is not found)
int unit = candidate.m_Component->GetUnitSelection( &sheetpath );
int unitcount = candidate.m_Component->GetUnitCount();
candidate.m_IsOrphan = ( unitcount == 0 );
if( unitcount > 1 || unit > 1 )
{
candidate.m_Reference << wxChar( ('A' + unit -1) );
}
m_components.push_back( candidate );
}
#endif
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;
bool mark_cell = m_components.front().m_IsOrphan;
CMP_CANDIDATE* cmp = nullptr;
for( unsigned ii = 0; ii < m_components.size(); ii++ )
{
cmp = &m_components[ii];
wxString str_libid = cmp->GetStringLibId();
if( last_str_libid != str_libid )
{
// Add last group to grid
AddRowToGrid( row, mark_cell, refs, last_str_libid );
// prepare next entry
mark_cell = cmp->m_IsOrphan;
last_str_libid = str_libid;
refs.Empty();
row++;
}
if( !refs.IsEmpty() )
refs += " ";
refs += cmp->GetSchematicReference();
cmp->m_Row = row;
}
// Add last component group:
AddRowToGrid( row, mark_cell, refs, last_str_libid );
m_grid->AutoSizeColumn( COL_CURR_LIBID );
// ensure the column title is correctly displayed
// (the min size is already fixed by AutoSizeColumn() )
m_grid->AutoSizeColLabelSize( COL_CURR_LIBID );
// Gives a similar width to COL_NEW_LIBID because it can contains similar strings
if( m_grid->GetColSize( COL_CURR_LIBID ) > m_grid->GetColSize( COL_NEW_LIBID ) )
m_grid->SetColSize( COL_NEW_LIBID, m_grid->GetColSize( COL_CURR_LIBID ) );
// ensure the column title is correctly displayed
m_grid->SetColMinimalWidth( COL_NEW_LIBID, m_grid->GetColSize( COL_NEW_LIBID ) );
m_grid->AutoSizeColLabelSize( COL_NEW_LIBID );
// Allows only the selection by row
m_grid->SetSelectionMode( wxGrid::wxGridSelectRows );
m_buttonOrphanItems->Enable( m_OrphansRowIndexes.size() > 0 );
Layout();
}
void DIALOG_EDIT_COMPONENTS_LIBID::AddRowToGrid( int aRowId, bool aMarkRow,
const wxString& aReferences, const wxString& aStrLibId )
{
if( aMarkRow ) // a orphan component exists, set m_AsOrphanCmp as true
m_OrphansRowIndexes.push_back( aRowId );
int row = aRowId;
if( m_grid->GetNumberRows() <= row )
m_grid->AppendRows();
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, new wxGridCellAutoWrapStringRenderer);
m_grid->AutoSizeRow( row, false );
}
bool DIALOG_EDIT_COMPONENTS_LIBID::validateLibIds()
{
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 at row %d!" ), new_libid, row+1 );
wxMessageBox( msg );
return false;
}
}
return true;
}
void DIALOG_EDIT_COMPONENTS_LIBID::onApplyButton( wxCommandEvent& event )
{
if( TransferDataFromWindow() )
m_parent->GetCanvas()->Refresh();
}
void DIALOG_EDIT_COMPONENTS_LIBID::onUndoChangesButton( wxCommandEvent& event )
{
revertChanges();
int row_max = m_grid->GetNumberRows() - 1;
for( int row = 0; row <= row_max; row++ )
{
m_grid->SetCellValue( row, COL_NEW_LIBID, wxEmptyString );
}
m_isModified = false;
}
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::onButtonBrowseLibraries( wxCommandEvent& event )
{
wxArrayInt rows = m_grid->GetSelectedRows();
if( rows.GetCount() != 1 ) // Should not occur, because the button is disabled
return;
setLibIdByBrowser( rows[0] );
}
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
for( unsigned ii = 0; ii < m_OrphansRowIndexes.size(); ii++ )
{
wxString orphanLibid = m_grid->GetCellValue( m_OrphansRowIndexes[ii], COL_CURR_LIBID );
int grid_row_idx = m_OrphansRowIndexes[ii]; //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
{
wxMessageBox( wxString::Format( _( "%u link(s) mapped, %d not found" ),
fixesCount, 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_parent->SelectComponentFromLibrary( NULL, dummy, true, 0, 0, false );
#else
// Use library viewer to choose a symbol
LIB_ID aPreselectedLibid;
SCH_BASE_FRAME::COMPONENT_SELECTION sel =
m_parent->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;
bool change = 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.
// set this new value
LIB_ID id;
id.Parse( new_libid, LIB_ID::ID_SCH, true );
for( CMP_CANDIDATE& cmp : m_components )
{
if( cmp.m_Row != row )
continue;
cmp.m_Component->SetLibId( id );
change = true;
cmp.m_Screen->SetModify();
}
}
if( change )
{
m_isModified = true;
SCH_SCREENS schematic;
schematic.UpdateSymbolLinks( true );
}
return true;
}
void DIALOG_EDIT_COMPONENTS_LIBID::revertChanges()
{
bool change = false;
int row_max = m_grid->GetNumberRows() - 1;
for( int row = 0; row <= row_max; row++ )
{
for( CMP_CANDIDATE& cmp : m_components )
{
if( cmp.m_Row != row )
continue;
LIB_ID id;
id.Parse( cmp.m_InitialLibId, LIB_ID::ID_SCH, true );
if( cmp.m_Component->GetLibId() != id )
{
cmp.m_Component->SetLibId( id );
change = true;
}
}
}
if( change )
{
SCH_SCREENS schematic;
schematic.UpdateSymbolLinks( true );
m_parent->GetCanvas()->Refresh();
}
}
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();
}