2017-11-20 16:14:25 +00:00
|
|
|
/*
|
|
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
|
|
*
|
|
|
|
* Copyright 2017 Jean-Pierre Charras, jp.charras@wanadoo.fr
|
2018-01-03 02:52:35 +00:00
|
|
|
* Copyright 1992-2018 KiCad Developers, see AUTHORS.txt for contributors.
|
2017-11-20 16:14:25 +00:00
|
|
|
*
|
|
|
|
* 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
|
2017-11-20 16:14:25 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
#include <fctsys.h>
|
2018-01-30 10:49:51 +00:00
|
|
|
#include <sch_edit_frame.h>
|
2017-11-20 16:14:25 +00:00
|
|
|
#include <class_drawpanel.h>
|
|
|
|
#include <sch_component.h>
|
|
|
|
#include <sch_reference_list.h>
|
2017-11-23 18:48:49 +00:00
|
|
|
#include <pgm_base.h>
|
|
|
|
#include <symbol_lib_table.h>
|
2018-06-28 17:19:31 +00:00
|
|
|
#include <widgets/wx_grid.h>
|
2017-11-20 16:14:25 +00:00
|
|
|
|
2018-06-28 17:19:31 +00:00
|
|
|
#include <dialog_edit_components_libid_base.h>
|
|
|
|
#include <wx/tokenzr.h>
|
2017-12-16 13:50:37 +00:00
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
#define COL_REFS 0
|
|
|
|
#define COL_CURR_LIBID 1
|
|
|
|
#define COL_NEW_LIBID 2
|
|
|
|
|
2018-06-28 17:19:31 +00:00
|
|
|
// 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 3
|
|
|
|
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
// 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
|
2017-11-21 17:06:37 +00:00
|
|
|
bool m_IsOrphan; // true if a component has no corresponding symbol found in libs
|
2017-11-20 16:14:25 +00:00
|
|
|
|
|
|
|
CMP_CANDIDATE( SCH_COMPONENT* aComponent )
|
|
|
|
{
|
|
|
|
m_Component = aComponent;
|
|
|
|
m_InitialLibId = m_Component->GetLibId().Format();
|
|
|
|
m_Row = -1;
|
2017-11-21 17:06:37 +00:00
|
|
|
m_IsOrphan = false;
|
2017-12-04 09:20:05 +00:00
|
|
|
m_Screen = nullptr;
|
2017-11-20 16:14:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Returns a string like mylib:symbol_name from the LIB_ID of the component
|
|
|
|
wxString GetStringLibId()
|
|
|
|
{
|
2018-04-29 13:45:11 +00:00
|
|
|
return m_Component->GetLibId().GetUniStringLibId();
|
2017-11-20 16:14:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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:
|
2018-04-08 10:28:59 +00:00
|
|
|
* to move a symbol from a symbol library to another symbol library
|
2017-11-20 16:14:25 +00:00
|
|
|
* to change the nickname of a library
|
2018-04-08 10:28:59 +00:00
|
|
|
* globally replace the symbol used by a group of components by another symbol.
|
2017-11-20 16:14:25 +00:00
|
|
|
*/
|
|
|
|
class DIALOG_EDIT_COMPONENTS_LIBID : public DIALOG_EDIT_COMPONENTS_LIBID_BASE
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
DIALOG_EDIT_COMPONENTS_LIBID( SCH_EDIT_FRAME* aParent );
|
2018-06-28 17:19:31 +00:00
|
|
|
~DIALOG_EDIT_COMPONENTS_LIBID() override;
|
2017-11-20 16:14:25 +00:00
|
|
|
|
2017-11-23 18:48:49 +00:00
|
|
|
bool IsSchematicModified() { return m_isModified; }
|
2017-11-20 16:14:25 +00:00
|
|
|
|
|
|
|
private:
|
|
|
|
SCH_EDIT_FRAME* m_parent;
|
2017-11-22 16:53:56 +00:00
|
|
|
bool m_isModified; // set to true if the schematic is modified
|
2017-11-23 18:48:49 +00:00
|
|
|
std::vector<int> m_OrphansRowIndexes; // list of rows containing orphan lib_id
|
2017-11-20 16:14:25 +00:00
|
|
|
|
|
|
|
std::vector<CMP_CANDIDATE> m_components;
|
|
|
|
|
2018-06-28 17:19:31 +00:00
|
|
|
GRIDCELL_AUTOWRAP_STRINGRENDERER* m_autoWrapRenderer;
|
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
void initDlg();
|
|
|
|
|
2017-11-22 12:14:48 +00:00
|
|
|
/**
|
|
|
|
* 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)
|
|
|
|
*/
|
2018-06-28 17:19:31 +00:00
|
|
|
void AddRowToGrid( bool aMarkRow, const wxString& aReferences, const wxString& aStrLibId );
|
2017-11-22 12:14:48 +00:00
|
|
|
|
2017-11-22 16:53:56 +00:00
|
|
|
/// returns true if all new lib id are valid
|
2017-11-20 16:14:25 +00:00
|
|
|
bool validateLibIds();
|
|
|
|
|
2017-11-22 16:53:56 +00:00
|
|
|
/// Reverts all changes already made
|
2017-11-20 16:14:25 +00:00
|
|
|
void revertChanges();
|
|
|
|
|
2017-11-23 09:39:52 +00:00
|
|
|
/** 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 );
|
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
// Events handlers
|
2017-11-22 16:53:56 +00:00
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
// called on a right click or a left double click:
|
|
|
|
void onCellBrowseLib( wxGridEvent& event ) override;
|
2017-11-22 16:53:56 +00:00
|
|
|
|
|
|
|
// Apply changes, but do not close the dialog
|
2017-11-20 16:14:25 +00:00
|
|
|
void onApplyButton( wxCommandEvent& event ) override;
|
|
|
|
|
2017-11-22 16:53:56 +00:00
|
|
|
// Cancel all changes, and close the dialog
|
2017-11-20 16:14:25 +00:00
|
|
|
void onCancel( wxCommandEvent& event ) override
|
|
|
|
{
|
|
|
|
if( m_isModified )
|
|
|
|
revertChanges();
|
|
|
|
event.Skip();
|
|
|
|
}
|
|
|
|
|
2017-11-22 16:53:56 +00:00
|
|
|
// Undo all changes, and clear the list of new lib_ids
|
2017-11-20 16:14:25 +00:00
|
|
|
void onUndoChangesButton( wxCommandEvent& event ) override;
|
|
|
|
|
2017-11-23 18:48:49 +00:00
|
|
|
// Try to find a candidate for non existing symbols
|
|
|
|
void onClickOrphansButton( wxCommandEvent& event ) override;
|
|
|
|
|
2017-11-23 09:39:52 +00:00
|
|
|
// UI event, to enable/disable buttons
|
2017-11-22 16:53:56 +00:00
|
|
|
void updateUIChangesButton( wxUpdateUIEvent& event ) override
|
|
|
|
{
|
|
|
|
m_buttonUndo->Enable( m_isModified );
|
|
|
|
}
|
|
|
|
|
|
|
|
// Automatically called when click on OK button
|
2017-11-20 16:14:25 +00:00
|
|
|
bool TransferDataFromWindow() override;
|
2018-06-28 17:19:31 +00:00
|
|
|
|
|
|
|
void AdjustGridColumns( int aWidth );
|
|
|
|
|
|
|
|
void OnSizeGrid( wxSizeEvent& event ) override;
|
2017-11-20 16:14:25 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
DIALOG_EDIT_COMPONENTS_LIBID::DIALOG_EDIT_COMPONENTS_LIBID( SCH_EDIT_FRAME* aParent )
|
|
|
|
:DIALOG_EDIT_COMPONENTS_LIBID_BASE( aParent )
|
|
|
|
{
|
|
|
|
m_parent = aParent;
|
2018-06-28 17:19:31 +00:00
|
|
|
m_autoWrapRenderer = new GRIDCELL_AUTOWRAP_STRINGRENDERER;
|
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
initDlg();
|
|
|
|
|
2018-06-28 17:19:31 +00:00
|
|
|
FinishDialogSettings();
|
|
|
|
}
|
2018-04-21 15:53:54 +00:00
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
|
2018-06-28 17:19:31 +00:00
|
|
|
DIALOG_EDIT_COMPONENTS_LIBID::~DIALOG_EDIT_COMPONENTS_LIBID()
|
|
|
|
{
|
|
|
|
m_autoWrapRenderer->DecRef();
|
2017-11-20 16:14:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-06-28 17:19:31 +00:00
|
|
|
// A sort compare function to sort components list by LIB_ID and then reference
|
2017-11-20 16:14:25 +00:00
|
|
|
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()
|
|
|
|
{
|
2018-06-28 17:19:31 +00:00
|
|
|
// Clear the FormBuilder rows
|
|
|
|
m_grid->DeleteRows( 0, m_grid->GetNumberRows() );
|
|
|
|
|
2017-11-22 16:53:56 +00:00
|
|
|
m_isModified = false;
|
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
// 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;
|
2017-11-21 17:06:37 +00:00
|
|
|
// 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 );
|
2017-11-20 16:14:25 +00:00
|
|
|
|
|
|
|
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();
|
2017-11-23 18:48:49 +00:00
|
|
|
candidate.m_IsOrphan = ( unitcount == 0 );
|
2017-11-20 16:14:25 +00:00
|
|
|
|
|
|
|
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;
|
2017-11-23 18:48:49 +00:00
|
|
|
bool mark_cell = m_components.front().m_IsOrphan;
|
2017-11-22 12:14:48 +00:00
|
|
|
CMP_CANDIDATE* cmp = nullptr;
|
2017-11-20 16:14:25 +00:00
|
|
|
|
|
|
|
for( unsigned ii = 0; ii < m_components.size(); ii++ )
|
|
|
|
{
|
2017-11-22 12:14:48 +00:00
|
|
|
cmp = &m_components[ii];
|
2017-11-20 16:14:25 +00:00
|
|
|
|
2017-11-22 12:14:48 +00:00
|
|
|
wxString str_libid = cmp->GetStringLibId();
|
2017-11-20 16:14:25 +00:00
|
|
|
|
2017-11-22 12:14:48 +00:00
|
|
|
if( last_str_libid != str_libid )
|
2017-11-20 16:14:25 +00:00
|
|
|
{
|
2017-11-22 12:14:48 +00:00
|
|
|
// Add last group to grid
|
2018-06-28 17:19:31 +00:00
|
|
|
AddRowToGrid( mark_cell, refs, last_str_libid );
|
2017-11-20 16:14:25 +00:00
|
|
|
|
|
|
|
// prepare next entry
|
2017-11-22 12:14:48 +00:00
|
|
|
mark_cell = cmp->m_IsOrphan;
|
2017-11-20 16:14:25 +00:00
|
|
|
last_str_libid = str_libid;
|
|
|
|
refs.Empty();
|
|
|
|
row++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !refs.IsEmpty() )
|
2018-06-28 17:19:31 +00:00
|
|
|
refs += wxT( ", " );
|
2017-11-20 16:14:25 +00:00
|
|
|
|
2017-11-22 12:14:48 +00:00
|
|
|
refs += cmp->GetSchematicReference();
|
|
|
|
cmp->m_Row = row;
|
2017-11-20 16:14:25 +00:00
|
|
|
}
|
|
|
|
|
2017-11-22 12:14:48 +00:00
|
|
|
// Add last component group:
|
2018-06-28 17:19:31 +00:00
|
|
|
AddRowToGrid( mark_cell, refs, last_str_libid );
|
2017-11-23 09:39:52 +00:00
|
|
|
|
|
|
|
// Allows only the selection by row
|
|
|
|
m_grid->SetSelectionMode( wxGrid::wxGridSelectRows );
|
2017-11-23 18:48:49 +00:00
|
|
|
|
|
|
|
m_buttonOrphanItems->Enable( m_OrphansRowIndexes.size() > 0 );
|
2017-12-24 15:04:02 +00:00
|
|
|
Layout();
|
2017-11-20 16:14:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-06-28 17:19:31 +00:00
|
|
|
void DIALOG_EDIT_COMPONENTS_LIBID::AddRowToGrid( bool aMarkRow, const wxString& aReferences,
|
|
|
|
const wxString& aStrLibId )
|
2017-11-22 12:14:48 +00:00
|
|
|
{
|
2018-06-28 17:19:31 +00:00
|
|
|
int row = m_grid->GetNumberRows();
|
2017-11-23 18:48:49 +00:00
|
|
|
|
2018-06-28 17:19:31 +00:00
|
|
|
if( aMarkRow ) // a orphan component exists, set m_AsOrphanCmp as true
|
|
|
|
m_OrphansRowIndexes.push_back( row );
|
2017-11-22 12:14:48 +00:00
|
|
|
|
2018-06-28 17:19:31 +00:00
|
|
|
m_grid->AppendRows( 1 );
|
2017-11-22 12:14:48 +00:00
|
|
|
|
|
|
|
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 );
|
|
|
|
}
|
|
|
|
|
2018-06-28 17:19:31 +00:00
|
|
|
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 ) );
|
2017-11-22 12:14:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
bool DIALOG_EDIT_COMPONENTS_LIBID::validateLibIds()
|
|
|
|
{
|
2018-08-15 13:29:19 +00:00
|
|
|
if( !m_grid->CommitPendingChanges() )
|
|
|
|
return false;
|
2018-06-28 17:19:31 +00:00
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
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;
|
2018-07-26 14:38:30 +00:00
|
|
|
id.Parse( new_libid, LIB_ID::ID_SCH );
|
2017-11-20 16:14:25 +00:00
|
|
|
|
|
|
|
if( !id.IsValid() )
|
|
|
|
{
|
|
|
|
wxString msg;
|
2018-06-28 17:19:31 +00:00
|
|
|
msg.Printf( _( "Symbol library identifier \"%s\" is not valid." ), new_libid );
|
2017-11-20 16:14:25 +00:00
|
|
|
wxMessageBox( msg );
|
2018-06-28 17:19:31 +00:00
|
|
|
|
|
|
|
m_grid->SetFocus();
|
|
|
|
m_grid->MakeCellVisible( row, COL_NEW_LIBID );
|
|
|
|
m_grid->SetGridCursor( row, COL_NEW_LIBID );
|
|
|
|
|
|
|
|
m_grid->EnableCellEditControl( true );
|
|
|
|
m_grid->ShowCellEditControl();
|
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
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();
|
2017-11-23 09:39:52 +00:00
|
|
|
m_grid->SelectRow( row ); // only for user, to show the selected line
|
2017-11-20 16:14:25 +00:00
|
|
|
|
2017-11-23 09:39:52 +00:00
|
|
|
setLibIdByBrowser( row );
|
|
|
|
|
|
|
|
}
|
2017-11-20 16:14:25 +00:00
|
|
|
|
|
|
|
|
2017-11-23 18:48:49 +00:00
|
|
|
void DIALOG_EDIT_COMPONENTS_LIBID::onClickOrphansButton( wxCommandEvent& event )
|
|
|
|
{
|
|
|
|
std::vector< wxString > libs = Prj().SchSymbolLibTable()->GetLogicalLibs();
|
|
|
|
wxArrayString aliasNames;
|
2017-12-16 13:50:37 +00:00
|
|
|
wxArrayString candidateSymbNames;
|
2017-11-23 18:48:49 +00:00
|
|
|
|
|
|
|
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 );
|
2017-12-16 13:50:37 +00:00
|
|
|
int grid_row_idx = m_OrphansRowIndexes[ii]; //row index in m_grid for the current item
|
2017-11-23 18:48:49 +00:00
|
|
|
|
2018-07-26 14:38:30 +00:00
|
|
|
LIB_ID curr_libid;
|
|
|
|
curr_libid.Parse( orphanLibid, LIB_ID::ID_SCH, true );
|
2017-11-23 18:48:49 +00:00
|
|
|
wxString symbName = curr_libid.GetLibItemName();
|
2017-12-16 13:50:37 +00:00
|
|
|
// 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();
|
2017-11-23 18:48:49 +00:00
|
|
|
|
2018-07-26 14:38:30 +00:00
|
|
|
// now try to find a candidate
|
2017-11-23 18:48:49 +00:00
|
|
|
for( auto &lib : libs )
|
|
|
|
{
|
|
|
|
aliasNames.Clear();
|
|
|
|
|
|
|
|
try
|
|
|
|
{
|
|
|
|
Prj().SchSymbolLibTable()->EnumerateSymbolLib( lib, aliasNames );
|
|
|
|
}
|
2018-04-18 07:10:29 +00:00
|
|
|
catch( const IO_ERROR& ) {} // ignore, it is handled below
|
2017-11-23 18:48:49 +00:00
|
|
|
|
|
|
|
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!
|
2017-12-16 13:50:37 +00:00
|
|
|
libIdCandidateCount++;
|
2017-11-23 18:48:49 +00:00
|
|
|
wxString newLibid = lib + ':' + symbName;
|
2017-12-16 13:50:37 +00:00
|
|
|
|
|
|
|
// 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 );
|
|
|
|
}
|
2017-11-23 18:48:49 +00:00
|
|
|
}
|
|
|
|
}
|
2017-12-16 13:50:37 +00:00
|
|
|
|
|
|
|
// 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() );
|
|
|
|
}
|
2017-11-23 18:48:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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 ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-23 09:39:52 +00:00
|
|
|
bool DIALOG_EDIT_COMPONENTS_LIBID::setLibIdByBrowser( int aRow )
|
|
|
|
{
|
|
|
|
#if 0
|
2017-11-23 18:48:49 +00:00
|
|
|
// Use dialog symbol selector to choose a symbol
|
2017-11-23 09:39:52 +00:00
|
|
|
SCH_BASE_FRAME::HISTORY_LIST dummy;
|
|
|
|
SCH_BASE_FRAME::COMPONENT_SELECTION sel =
|
2018-05-14 17:34:18 +00:00
|
|
|
m_frame->SelectComponentFromLibrary( NULL, dummy, true, 0, 0, false );
|
2017-11-23 09:39:52 +00:00
|
|
|
#else
|
2017-11-23 18:48:49 +00:00
|
|
|
// Use library viewer to choose a symbol
|
2017-11-23 09:39:52 +00:00
|
|
|
LIB_ID aPreselectedLibid;
|
|
|
|
SCH_BASE_FRAME::COMPONENT_SELECTION sel =
|
2018-01-01 09:41:43 +00:00
|
|
|
m_parent->SelectComponentFromLibBrowser( this, NULL, aPreselectedLibid, 0, 0 );
|
2017-11-23 09:39:52 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
if( sel.LibId.empty() ) // command aborted
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if( !sel.LibId.IsValid() ) // Should not occur
|
|
|
|
{
|
|
|
|
wxMessageBox( _( "Invalid symbol library identifier" ) );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
wxString new_libid;
|
|
|
|
new_libid = sel.LibId.Format();
|
|
|
|
|
2017-11-23 09:39:52 +00:00
|
|
|
m_grid->SetCellValue( aRow, COL_NEW_LIBID, new_libid );
|
2017-11-20 16:14:25 +00:00
|
|
|
|
2017-11-23 09:39:52 +00:00
|
|
|
return true;
|
2017-11-20 16:14:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
2018-07-26 14:38:30 +00:00
|
|
|
id.Parse( new_libid, LIB_ID::ID_SCH, true );
|
2017-11-20 16:14:25 +00:00
|
|
|
|
|
|
|
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;
|
2018-07-26 14:38:30 +00:00
|
|
|
id.Parse( cmp.m_InitialLibId, LIB_ID::ID_SCH, true );
|
2017-11-20 16:14:25 +00:00
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-06-28 17:19:31 +00:00
|
|
|
void DIALOG_EDIT_COMPONENTS_LIBID::AdjustGridColumns( int aWidth )
|
|
|
|
{
|
|
|
|
// Account for scroll bars
|
|
|
|
aWidth -= ( m_grid->GetSize().x - m_grid->GetClientSize().x );
|
|
|
|
|
|
|
|
m_grid->SetColSize( 0, aWidth / 3 );
|
|
|
|
m_grid->SetColSize( 1, aWidth / 3 );
|
|
|
|
m_grid->SetColSize( 2, aWidth - m_grid->GetColSize( 0 ) - m_grid->GetColSize( 1 ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-11-20 16:14:25 +00:00
|
|
|
bool InvokeDialogEditComponentsLibId( SCH_EDIT_FRAME* aCaller )
|
|
|
|
{
|
2018-01-01 12:17:06 +00:00
|
|
|
// 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.
|
2017-11-20 16:14:25 +00:00
|
|
|
DIALOG_EDIT_COMPONENTS_LIBID dlg( aCaller );
|
2018-01-01 12:17:06 +00:00
|
|
|
// DO NOT use ShowModal() here, otherwise the library browser will not work
|
|
|
|
// properly.
|
|
|
|
dlg.ShowQuasiModal();
|
2017-11-20 16:14:25 +00:00
|
|
|
|
2017-11-23 18:48:49 +00:00
|
|
|
return dlg.IsSchematicModified();
|
2017-12-24 15:04:02 +00:00
|
|
|
}
|