kicad-source/eeschema/dialogs/dialog_symbol_fields_table.cpp

2527 lines
79 KiB
C++
Raw Normal View History

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2017 Oliver Walters
* Copyright The 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
*/
#include <common.h>
#include <base_units.h>
#include <bitmaps.h>
#include <symbol_library.h>
#include <confirm.h>
#include <eda_doc.h>
#include <wildcards_and_files_ext.h>
#include <schematic_settings.h>
#include <general.h>
#include <grid_tricks.h>
#include <string_utils.h>
2021-09-14 23:45:14 +01:00
#include <kiface_base.h>
#include <sch_commit.h>
#include <sch_edit_frame.h>
#include <sch_reference_list.h>
#include <tools/sch_editor_control.h>
2021-12-16 07:56:21 +03:00
#include <kiplatform/ui.h>
#include <widgets/grid_text_button_helpers.h>
#include <widgets/bitmap_button.h>
#include <widgets/std_bitmap_button.h>
#include <widgets/wx_grid.h>
#include <widgets/grid_checkbox.h>
#include <wx/debug.h>
#include <wx/ffile.h>
#include <wx/grid.h>
#include <wx/textdlg.h>
#include <wx/filedlg.h>
2024-04-27 22:57:24 +03:00
#include <wx/msgdlg.h>
#include <dialogs/eda_view_switcher.h>
2021-07-03 21:43:30 +01:00
#include "dialog_symbol_fields_table.h"
#include <fields_data_model.h>
#include <eda_list_dialog.h>
#include <project_sch.h>
#include <jobs/job_export_sch_bom.h>
wxDEFINE_EVENT( EDA_EVT_CLOSE_DIALOG_SYMBOL_FIELDS_TABLE, wxCommandEvent );
#ifdef __WXMAC__
#define COLUMN_MARGIN 3
#else
#define COLUMN_MARGIN 15
#endif
using SCOPE = FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE;
enum
{
MYID_SELECT_FOOTPRINT = GRIDTRICKS_FIRST_CLIENT_ID,
MYID_SHOW_DATASHEET
};
class VIEW_CONTROLS_GRID_TRICKS : public GRID_TRICKS
{
public:
VIEW_CONTROLS_GRID_TRICKS( WX_GRID* aGrid ) :
GRID_TRICKS( aGrid )
{}
protected:
void doPopupSelection( wxCommandEvent& event ) override
{
if( event.GetId() >= GRIDTRICKS_FIRST_SHOWHIDE )
m_grid->PostSizeEvent();
GRID_TRICKS::doPopupSelection( event );
}
};
class FIELDS_EDITOR_GRID_TRICKS : public GRID_TRICKS
{
public:
FIELDS_EDITOR_GRID_TRICKS( DIALOG_SYMBOL_FIELDS_TABLE* aParent, WX_GRID* aGrid,
VIEW_CONTROLS_GRID_DATA_MODEL* aViewFieldsData,
FIELDS_EDITOR_GRID_DATA_MODEL* aDataModel, EMBEDDED_FILES* aFiles ) :
GRID_TRICKS( aGrid ),
m_dlg( aParent ),
m_viewControlsDataModel( aViewFieldsData ),
m_dataModel( aDataModel ),
m_files( aFiles )
{}
protected:
void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override
{
int col = m_grid->GetGridCursorCol();
if( m_dataModel->GetColFieldName( col ) == GetCanonicalFieldName( FIELD_T::FOOTPRINT ) )
{
2025-04-28 12:02:43 +01:00
menu.Append( MYID_SELECT_FOOTPRINT, _( "Select Footprint..." ), _( "Browse for footprint" ) );
menu.AppendSeparator();
}
else if( m_dataModel->GetColFieldName( col ) == GetCanonicalFieldName( FIELD_T::DATASHEET ) )
{
2025-04-28 12:02:43 +01:00
menu.Append( MYID_SHOW_DATASHEET, _( "Show Datasheet" ), _( "Show datasheet in browser" ) );
menu.AppendSeparator();
}
GRID_TRICKS::showPopupMenu( menu, aEvent );
}
void doPopupSelection( wxCommandEvent& event ) override
{
int row = m_grid->GetGridCursorRow();
int col = m_grid->GetGridCursorCol();
if( event.GetId() == MYID_SELECT_FOOTPRINT )
{
// pick a footprint using the footprint picker.
wxString fpid = m_grid->GetCellValue( row, col );
if( KIWAY_PLAYER* frame = m_dlg->Kiway().Player( FRAME_FOOTPRINT_CHOOSER, true, m_dlg ) )
{
if( frame->ShowModal( &fpid, m_dlg ) )
m_grid->SetCellValue( row, col, fpid );
frame->Destroy();
}
}
else if (event.GetId() == MYID_SHOW_DATASHEET )
{
wxString datasheet_uri = m_grid->GetCellValue( row, col );
GetAssociatedDocument( m_dlg, datasheet_uri, &m_dlg->Prj(), PROJECT_SCH::SchSearchS( &m_dlg->Prj() ),
{ m_files } );
}
else if( event.GetId() >= GRIDTRICKS_FIRST_SHOWHIDE )
{
if( !m_grid->CommitPendingChanges( false ) )
return;
// Pop-up column order is the order of the shown fields, not the viewControls order
col = event.GetId() - GRIDTRICKS_FIRST_SHOWHIDE;
bool show = !m_dataModel->GetShowColumn( col );
m_dlg->ShowHideColumn( col, show );
wxString fieldName = m_dataModel->GetColFieldName( col );
for( row = 0; row < m_viewControlsDataModel->GetNumberRows(); row++ )
{
if( m_viewControlsDataModel->GetCanonicalFieldName( row ) == fieldName )
m_viewControlsDataModel->SetValueAsBool( row, SHOW_FIELD_COLUMN, show );
}
if( m_viewControlsDataModel->GetView() )
m_viewControlsDataModel->GetView()->ForceRefresh();
}
else
{
GRID_TRICKS::doPopupSelection( event );
}
}
private:
DIALOG_SYMBOL_FIELDS_TABLE* m_dlg;
VIEW_CONTROLS_GRID_DATA_MODEL* m_viewControlsDataModel;
FIELDS_EDITOR_GRID_DATA_MODEL* m_dataModel;
EMBEDDED_FILES* m_files;
};
DIALOG_SYMBOL_FIELDS_TABLE::DIALOG_SYMBOL_FIELDS_TABLE( SCH_EDIT_FRAME* parent, JOB_EXPORT_SCH_BOM* aJob ) :
2024-05-06 10:47:04 +01:00
DIALOG_SYMBOL_FIELDS_TABLE_BASE( parent ),
m_currentBomPreset( nullptr ),
m_lastSelectedBomPreset( nullptr ),
m_parent( parent ),
m_viewControlsDataModel( nullptr ),
m_dataModel( nullptr ),
m_schSettings( parent->Schematic().Settings() ),
m_job( aJob )
{
// Get all symbols from the list of schematic sheets
m_parent->Schematic().Hierarchy().GetSymbols( m_symbolsList, false );
m_bRefresh->SetBitmap( KiBitmapBundle( BITMAPS::small_refresh ) );
m_bMenu->SetBitmap( KiBitmapBundle( BITMAPS::config ) );
m_bRefreshPreview->SetBitmap( KiBitmapBundle( BITMAPS::small_refresh ) );
m_browseButton->SetBitmap( KiBitmapBundle( BITMAPS::small_folder ) );
m_addFieldButton->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
m_removeFieldButton->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
m_renameFieldButton->SetBitmap( KiBitmapBundle( BITMAPS::small_edit ) );
m_viewControlsDataModel = new VIEW_CONTROLS_GRID_DATA_MODEL();
m_viewControlsGrid->UseNativeColHeader( true );
m_viewControlsGrid->SetTable( m_viewControlsDataModel, true );
// must be done after SetTable(), which appears to re-set it
m_viewControlsGrid->SetSelectionMode( wxGrid::wxGridSelectCells );
// add Cut, Copy, and Paste to wxGrid
m_viewControlsGrid->PushEventHandler( new VIEW_CONTROLS_GRID_TRICKS( m_viewControlsGrid ) );
wxGridCellAttr* attr = new wxGridCellAttr;
attr->SetReadOnly( true );
m_viewControlsDataModel->SetColAttr( attr, DISPLAY_NAME_COLUMN );
attr = new wxGridCellAttr;
attr->SetRenderer( new wxGridCellBoolRenderer() );
attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
m_viewControlsDataModel->SetColAttr( attr, SHOW_FIELD_COLUMN );
attr = new wxGridCellAttr;
attr->SetRenderer( new wxGridCellBoolRenderer() );
attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
m_viewControlsDataModel->SetColAttr( attr, GROUP_BY_COLUMN );
// Compress the view controls grid. (We want it to look different from the fields grid.)
m_viewControlsGrid->SetDefaultRowSize( m_viewControlsGrid->GetDefaultRowSize() - FromDIP( 4 ) );
m_filter->SetDescriptiveText( _( "Filter" ) );
attr = new wxGridCellAttr;
attr->SetEditor( new GRID_CELL_URL_EDITOR( this, PROJECT_SCH::SchSearchS( &Prj() ), { &m_parent->Schematic() } ) );
m_dataModel = new FIELDS_EDITOR_GRID_DATA_MODEL( m_symbolsList, attr );
LoadFieldNames(); // loads rows into m_viewControlsDataModel and columns into m_dataModel
m_grid->UseNativeColHeader( true );
m_grid->SetTable( m_dataModel, true );
// must be done after SetTable(), which appears to re-set it
m_grid->SetSelectionMode( wxGrid::wxGridSelectCells );
// add Cut, Copy, and Paste to wxGrid
m_grid->PushEventHandler( new FIELDS_EDITOR_GRID_TRICKS( this, m_grid, m_viewControlsDataModel, m_dataModel,
&m_parent->Schematic() ) );
// Load our BOM view presets
SetUserBomPresets( m_schSettings.m_BomPresets );
BOM_PRESET preset = m_schSettings.m_BomSettings;
2025-01-01 16:32:36 +00:00
if( m_job )
{
SetTitle( m_job->GetSettingsDialogTitle() );
2025-01-01 16:32:36 +00:00
preset.name = m_job->m_bomPresetName;
preset.excludeDNP = m_job->m_excludeDNP;
preset.includeExcludedFromBOM = m_job->m_includeExcludedFromBOM;
preset.filterString = m_job->m_filterString;
preset.sortAsc = m_job->m_sortAsc;
preset.sortField = m_job->m_sortField;
preset.groupSymbols = ( m_job->m_fieldsGroupBy.size() > 0 );
preset.fieldsOrdered.clear();
size_t i = 0;
for( const wxString& fieldName : m_job->m_fieldsOrdered )
{
BOM_FIELD field;
field.name = fieldName;
field.show = !fieldName.StartsWith( wxT( "__" ), &field.name );
field.groupBy = alg::contains( m_job->m_fieldsGroupBy, field.name );
if( ( m_job->m_fieldsLabels.size() > i ) && !m_job->m_fieldsLabels[i].IsEmpty() )
field.label = m_job->m_fieldsLabels[i];
else if( IsGeneratedField( field.name ) )
field.label = GetGeneratedFieldDisplayName( field.name );
else
field.label = field.name;
preset.fieldsOrdered.emplace_back( field );
i++;
}
}
2025-01-01 16:32:36 +00:00
// DIALOG_SHIM needs a unique hash_key because classname will be the same for both job and
// non-job versions (which have different sizes).
m_hash_key = TO_UTF8( GetTitle() );
ApplyBomPreset( preset );
syncBomPresetSelection();
// Load BOM export format presets
SetUserBomFmtPresets( m_schSettings.m_BomFmtPresets );
BOM_FMT_PRESET fmtPreset = m_schSettings.m_BomFmtSettings;
if( m_job )
{
fmtPreset.name = m_job->m_bomFmtPresetName;
fmtPreset.fieldDelimiter = m_job->m_fieldDelimiter;
fmtPreset.keepLineBreaks = m_job->m_keepLineBreaks;
fmtPreset.keepTabs = m_job->m_keepTabs;
fmtPreset.refDelimiter = m_job->m_refDelimiter;
fmtPreset.refRangeDelimiter = m_job->m_refRangeDelimiter;
fmtPreset.stringDelimiter = m_job->m_stringDelimiter;
}
ApplyBomFmtPreset( fmtPreset );
syncBomFmtPresetSelection();
SetInitialFocus( m_grid );
m_grid->ClearSelection();
SetupStandardButtons();
finishDialogSettings();
EESCHEMA_SETTINGS::PANEL_FIELD_EDITOR& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
wxSize dlgSize( cfg.width > 0 ? cfg.width : horizPixelsFromDU( 600 ),
cfg.height > 0 ? cfg.height : vertPixelsFromDU( 300 ) );
SetSize( dlgSize );
m_nbPages->SetSelection( cfg.page );
m_viewControlsGrid->ShowHideColumns( cfg.view_controls_visible_columns );
CallAfter( [this, cfg]()
{
m_splitterMainWindow->SetSashPosition( cfg.sash_pos );
} );
if( m_job )
m_outputFileName->SetValue( m_job->GetConfiguredOutputPath() );
else
m_outputFileName->SetValue( m_schSettings.m_BomExportFileName );
Center();
// Connect Events
m_grid->Connect( wxEVT_GRID_COL_SORT, wxGridEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnColSort ), nullptr, this );
m_grid->Connect( wxEVT_GRID_COL_MOVE, wxGridEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnColMove ), nullptr, this );
m_cbBomPresets->Bind( wxEVT_CHOICE, &DIALOG_SYMBOL_FIELDS_TABLE::onBomPresetChanged, this );
2025-04-28 12:02:43 +01:00
m_cbBomFmtPresets->Bind( wxEVT_CHOICE, &DIALOG_SYMBOL_FIELDS_TABLE::onBomFmtPresetChanged, this );
m_viewControlsGrid->Bind( wxEVT_GRID_CELL_CHANGED, &DIALOG_SYMBOL_FIELDS_TABLE::OnViewControlsCellChanged, this );
if( !m_job )
{
// Start listening for schematic changes
m_parent->Schematic().AddListener( this );
}
else
{
// Don't allow editing
m_grid->EnableEditing( false );
m_buttonApply->Hide();
m_buttonExport->Hide();
SetupStandardButtons();
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::SetupColumnProperties( int aCol )
{
wxGridCellAttr* attr = new wxGridCellAttr;
attr->SetReadOnly( false );
// Set some column types to specific editors
if( m_dataModel->ColIsReference( aCol ) )
{
attr->SetReadOnly();
m_dataModel->SetColAttr( attr, aCol );
}
else if( m_dataModel->GetColFieldName( aCol ) == GetCanonicalFieldName( FIELD_T::FOOTPRINT ) )
{
attr->SetEditor( new GRID_CELL_FPID_EDITOR( this, wxEmptyString ) );
m_dataModel->SetColAttr( attr, aCol );
}
else if( m_dataModel->GetColFieldName( aCol ) == GetCanonicalFieldName( FIELD_T::DATASHEET ) )
{
// set datasheet column viewer button
attr->SetEditor( new GRID_CELL_URL_EDITOR( this, PROJECT_SCH::SchSearchS( &Prj() ),
{ &m_parent->Schematic() } ) );
m_dataModel->SetColAttr( attr, aCol );
}
else if( m_dataModel->ColIsQuantity( aCol ) || m_dataModel->ColIsItemNumber( aCol ) )
{
attr->SetReadOnly();
attr->SetAlignment( wxALIGN_RIGHT, wxALIGN_CENTER );
attr->SetRenderer( new wxGridCellNumberRenderer() );
m_dataModel->SetColAttr( attr, aCol );
}
else if( m_dataModel->ColIsAttribute( aCol ) )
{
attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
attr->SetRenderer( new GRID_CELL_CHECKBOX_RENDERER() );
attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
m_dataModel->SetColAttr( attr, aCol );
}
else if( IsGeneratedField( m_dataModel->GetColFieldName( aCol ) ) )
{
attr->SetReadOnly();
m_dataModel->SetColAttr( attr, aCol );
}
else
{
attr->SetEditor( m_grid->GetDefaultEditor() );
m_dataModel->SetColAttr( attr, aCol );
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::SetupAllColumnProperties()
{
EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
wxSize defaultDlgSize = ConvertDialogToPixels( wxSize( 600, 300 ) );
// Restore column sorting order and widths
m_grid->AutoSizeColumns( false );
int sortCol = 0;
bool sortAscending = true;
for( int col = 0; col < m_grid->GetNumberCols(); ++col )
{
SetupColumnProperties( col );
if( col == m_dataModel->GetSortCol() )
{
sortCol = col;
sortAscending = m_dataModel->GetSortAsc();
}
}
// sync m_grid's column visibilities to Show checkboxes in m_viewControlsGrid
for( int i = 0; i < m_viewControlsDataModel->GetNumberRows(); ++i )
{
int col = m_dataModel->GetFieldNameCol( m_viewControlsDataModel->GetCanonicalFieldName( i ) );
if( col == -1 )
continue;
bool show = m_viewControlsDataModel->GetValueAsBool( i, SHOW_FIELD_COLUMN );
m_dataModel->SetShowColumn( col, show );
if( show )
{
m_grid->ShowCol( col );
std::string key( m_dataModel->GetColFieldName( col ).ToUTF8() );
if( cfg->m_FieldEditorPanel.field_widths.count( key )
&& ( cfg->m_FieldEditorPanel.field_widths.at( key ) > 0 ) )
{
m_grid->SetColSize( col, cfg->m_FieldEditorPanel.field_widths.at( key ) );
}
else
{
int textWidth = m_dataModel->GetDataWidth( col ) + COLUMN_MARGIN;
int maxWidth = defaultDlgSize.x / 3;
m_grid->SetColSize( col, std::clamp( textWidth, 100, maxWidth ) );
}
}
else
{
m_grid->HideCol( col );
}
}
m_dataModel->SetSorting( sortCol, sortAscending );
m_grid->SetSortingColumn( sortCol, sortAscending );
}
2021-07-03 21:43:30 +01:00
DIALOG_SYMBOL_FIELDS_TABLE::~DIALOG_SYMBOL_FIELDS_TABLE()
{
if( EESCHEMA_SETTINGS* cfg = dynamic_cast<EESCHEMA_SETTINGS*>( Kiface().KifaceSettings() ) )
{
cfg->m_FieldEditorPanel.view_controls_visible_columns = m_viewControlsGrid->GetShownColumnsAsString();
cfg->m_FieldEditorPanel.sash_pos = m_splitterMainWindow->GetSashPosition();
}
// Disconnect Events
2025-04-28 12:02:43 +01:00
m_grid->Disconnect( wxEVT_GRID_COL_SORT, wxGridEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnColSort ),
nullptr, this );
m_grid->Disconnect( wxEVT_GRID_COL_SORT, wxGridEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnColMove ),
nullptr, this );
// Delete the GRID_TRICKS.
m_viewControlsGrid->PopEventHandler( true );
m_grid->PopEventHandler( true );
// we gave ownership of m_viewControlsDataModel & m_dataModel to the wxGrids...
}
2021-07-03 21:43:30 +01:00
bool DIALOG_SYMBOL_FIELDS_TABLE::TransferDataToWindow()
{
if( !wxDialog::TransferDataFromWindow() )
return false;
2025-03-12 14:41:25 +00:00
TOOL_MANAGER* toolMgr = m_parent->GetToolManager();
SCH_SELECTION_TOOL* selectionTool = toolMgr->GetTool<SCH_SELECTION_TOOL>();
SCH_SELECTION& selection = selectionTool->GetSelection();
SCH_SYMBOL* symbol = nullptr;
EESCHEMA_SETTINGS::PANEL_FIELD_EDITOR& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
switch( cfg.scope )
{
case SCOPE::SCOPE_ALL: m_scope->SetSelection( 0 ); break;
case SCOPE::SCOPE_SHEET: m_scope->SetSelection( 1 ); break;
case SCOPE::SCOPE_SHEET_RECURSIVE: m_scope->SetSelection( 2 ); break;
}
setScope( static_cast<SCOPE>( cfg.scope ) );
if( selection.GetSize() == 1 )
{
2024-05-06 10:47:04 +01:00
EDA_ITEM* item = selection.Front();
2021-06-10 10:10:55 -04:00
if( item->Type() == SCH_SYMBOL_T )
symbol = (SCH_SYMBOL*) item;
else if( item->GetParent() && item->GetParent()->Type() == SCH_SYMBOL_T )
symbol = (SCH_SYMBOL*) item->GetParent();
}
if( symbol )
{
for( int row = 0; row < m_dataModel->GetNumberRows(); ++row )
{
std::vector<SCH_REFERENCE> references = m_dataModel->GetRowReferences( row );
bool found = false;
2020-01-09 22:55:46 +00:00
for( const SCH_REFERENCE& ref : references )
{
if( ref.GetSymbol() == symbol )
{
found = true;
break;
}
}
if( found )
{
// Find the value column and the reference column if they're shown
int valueCol = -1;
int refCol = -1;
int anyCol = -1;
for( int col = 0; col < m_dataModel->GetNumberCols(); col++ )
{
if( m_dataModel->ColIsValue( col ) )
valueCol = col;
else if( m_dataModel->ColIsReference( col ) )
refCol = col;
else if( anyCol == -1 && m_dataModel->GetShowColumn( col ) )
anyCol = col;
}
if( valueCol != -1 && m_dataModel->GetShowColumn( valueCol ) )
m_grid->GoToCell( row, valueCol );
else if( refCol != -1 && m_dataModel->GetShowColumn( refCol ) )
m_grid->GoToCell( row, refCol );
else if( anyCol != -1 )
m_grid->GoToCell( row, anyCol );
break;
}
}
}
// We don't want table range selection events to happen until we've loaded the data or we
// we'll clear our selection as the grid is built before the code above can get the
// user's current selection.
EnableSelectionEvents();
return true;
}
2021-07-03 21:43:30 +01:00
bool DIALOG_SYMBOL_FIELDS_TABLE::TransferDataFromWindow()
{
if( !m_grid->CommitPendingChanges() )
return false;
if( !wxDialog::TransferDataFromWindow() )
return false;
if( m_job )
{
// and exit, don't even dream of saving changes from the data model
return true;
}
SCH_COMMIT commit( m_parent );
SCH_SHEET_PATH currentSheet = m_parent->GetCurrentSheet();
m_dataModel->ApplyData( commit, m_schSettings.m_TemplateFieldNames );
commit.Push( wxS( "Symbol Fields Table Edit" ) );
// Reset the view to where we left the user
m_parent->SetCurrentSheet( currentSheet );
m_parent->SyncView();
m_parent->Refresh();
m_parent->OnModify();
return true;
}
2023-03-14 20:03:57 -04:00
void DIALOG_SYMBOL_FIELDS_TABLE::AddField( const wxString& aFieldName, const wxString& aLabelValue,
bool show, bool groupBy, bool addedByUser )
{
// Users can add fields with variable names that match the special names in the grid,
// e.g. ${QUANTITY} so make sure we don't add them twice
for( int row = 0; row < m_viewControlsDataModel->GetNumberRows(); row++ )
2024-01-23 12:37:02 +00:00
{
if( m_viewControlsDataModel->GetCanonicalFieldName( row ) == aFieldName )
return;
2024-01-23 12:37:02 +00:00
}
m_dataModel->AddColumn( aFieldName, aLabelValue, addedByUser );
wxGridTableMessage msg( m_dataModel, wxGRIDTABLE_NOTIFY_COLS_APPENDED, 1 );
m_grid->ProcessTableMessage( msg );
m_viewControlsGrid->OnAddRow(
[&]() -> std::pair<int, int>
{
m_viewControlsDataModel->AppendRow( aFieldName, aLabelValue, show, groupBy );
return { m_viewControlsDataModel->GetNumberRows() - 1, -1 };
} );
}
2021-07-03 21:43:30 +01:00
void DIALOG_SYMBOL_FIELDS_TABLE::LoadFieldNames()
{
auto addMandatoryField =
[&]( FIELD_T fieldId, bool show, bool groupBy )
{
m_mandatoryFieldListIndexes[fieldId] = m_viewControlsDataModel->GetNumberRows();
AddField( GetCanonicalFieldName( fieldId ), GetDefaultFieldName( fieldId, DO_TRANSLATE ),
show, groupBy );
};
// Add mandatory fields first show groupBy
addMandatoryField( FIELD_T::REFERENCE, true, true );
addMandatoryField( FIELD_T::VALUE, true, true );
addMandatoryField( FIELD_T::FOOTPRINT, true, true );
addMandatoryField( FIELD_T::DATASHEET, true, false );
addMandatoryField( FIELD_T::DESCRIPTION, false, false );
// Generated fields present only in the fields table
AddField( FIELDS_EDITOR_GRID_DATA_MODEL::QUANTITY_VARIABLE, _( "Qty" ), true, false );
AddField( FIELDS_EDITOR_GRID_DATA_MODEL::ITEM_NUMBER_VARIABLE, _( "#" ), true, false );
// User fields next
std::set<wxString> userFieldNames;
2025-04-28 12:02:43 +01:00
for( int ii = 0; ii < (int) m_symbolsList.GetCount(); ++ii )
{
2025-04-28 12:02:43 +01:00
SCH_SYMBOL* symbol = m_symbolsList[ii].GetSymbol();
for( const SCH_FIELD& field : symbol->GetFields() )
{
if( !field.IsMandatory() && !field.IsPrivate() )
userFieldNames.insert( field.GetName() );
}
}
2019-07-17 21:45:43 +01:00
for( const wxString& fieldName : userFieldNames )
AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), true, false );
// Add any templateFieldNames which aren't already present in the userFieldNames
2025-04-28 12:02:43 +01:00
for( const TEMPLATE_FIELDNAME& tfn : m_schSettings.m_TemplateFieldNames.GetTemplateFieldNames() )
{
2025-04-28 12:02:43 +01:00
if( userFieldNames.count( tfn.m_Name ) == 0 )
AddField( tfn.m_Name, GetGeneratedFieldDisplayName( tfn.m_Name ), false, false );
}
}
2021-07-03 21:43:30 +01:00
void DIALOG_SYMBOL_FIELDS_TABLE::OnAddField( wxCommandEvent& event )
{
wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Add Field" ) );
if( dlg.ShowModal() != wxID_OK )
return;
wxString fieldName = dlg.GetValue();
if( fieldName.IsEmpty() )
{
DisplayError( this, _( "Field must have a name." ) );
return;
}
for( int i = 0; i < m_dataModel->GetNumberCols(); ++i )
{
if( fieldName == m_dataModel->GetColFieldName( i ) )
{
2025-04-28 12:02:43 +01:00
DisplayError( this, wxString::Format( _( "Field name '%s' already in use." ), fieldName ) );
return;
}
}
AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), true, false, true );
SetupColumnProperties( m_dataModel->GetColsCount() - 1 );
syncBomPresetSelection();
OnModify();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnRemoveField( wxCommandEvent& event )
{
m_viewControlsGrid->OnDeleteRows(
[&]( int row )
{
for( FIELD_T id : MANDATORY_FIELDS )
{
if( m_mandatoryFieldListIndexes[id] == row )
{
DisplayError( this, wxString::Format( _( "The first %d fields are mandatory." ),
(int) m_mandatoryFieldListIndexes.size() ) );
return false;
}
}
return IsOK( this, wxString::Format( _( "Are you sure you want to remove the field '%s'?" ),
m_viewControlsDataModel->GetValue( row, DISPLAY_NAME_COLUMN ) ) );
},
[&]( int row )
{
wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
int col = m_dataModel->GetFieldNameCol( fieldName );
2022-09-11 12:28:53 +00:00
if( col != -1 )
m_dataModel->RemoveColumn( col );
m_viewControlsDataModel->DeleteRow( row );
syncBomPresetSelection();
OnModify();
} );
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnRenameField( wxCommandEvent& event )
{
wxArrayInt selectedRows = m_viewControlsGrid->GetSelectedRows();
if( selectedRows.empty() && m_viewControlsGrid->GetGridCursorRow() >= 0 )
selectedRows.push_back( m_viewControlsGrid->GetGridCursorRow() );
if( selectedRows.empty() )
return;
int row = selectedRows[0];
for( FIELD_T id : MANDATORY_FIELDS )
{
if( m_mandatoryFieldListIndexes[id] == row )
{
DisplayError( this, wxString::Format( _( "The first %d fields are mandatory and names cannot be changed." ),
(int) m_mandatoryFieldListIndexes.size() ) );
return;
}
}
wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
int col = m_dataModel->GetFieldNameCol( fieldName );
wxCHECK_RET( col != -1, wxS( "Existing field name missing from data model" ) );
wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Rename Field" ), fieldName );
if( dlg.ShowModal() != wxID_OK )
return;
wxString newFieldName = dlg.GetValue();
// No change, no-op
if( newFieldName == fieldName )
return;
// New field name already exists
if( m_dataModel->GetFieldNameCol( newFieldName ) != -1 )
{
2025-04-28 12:02:43 +01:00
wxString confirm_msg = wxString::Format( _( "Field name %s already exists." ), newFieldName );
DisplayError( this, confirm_msg );
return;
}
m_dataModel->RenameColumn( col, newFieldName );
m_viewControlsDataModel->SetCanonicalFieldName( row, newFieldName );
m_viewControlsDataModel->SetValue( row, DISPLAY_NAME_COLUMN, newFieldName );
m_viewControlsDataModel->SetValue( row, LABEL_COLUMN, GetGeneratedFieldDisplayName( newFieldName ) );
syncBomPresetSelection();
OnModify();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnFilterText( wxCommandEvent& aEvent )
{
m_dataModel->SetFilter( m_filter->GetValue() );
m_dataModel->RebuildRows();
m_grid->ForceRefresh();
syncBomPresetSelection();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnFilterMouseMoved( wxMouseEvent& aEvent )
{
#if defined( __WXOSX__ ) || wxCHECK_VERSION( 3, 3, 0 ) // Doesn't work properly on other ports
wxPoint pos = aEvent.GetPosition();
wxRect ctrlRect = m_filter->GetScreenRect();
int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
// TODO: restore cursor when mouse leaves the filter field (or is it a MSW bug?)
if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
SetCursor( wxCURSOR_ARROW );
else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
SetCursor( wxCURSOR_ARROW );
else
SetCursor( wxCURSOR_IBEAM );
#endif
}
2021-07-03 21:43:30 +01:00
void DIALOG_SYMBOL_FIELDS_TABLE::OnGroupSymbolsToggled( wxCommandEvent& event )
{
m_dataModel->SetGroupingEnabled( m_groupSymbolsBox->GetValue() );
m_dataModel->RebuildRows();
m_grid->ForceRefresh();
syncBomPresetSelection();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnMenu( wxCommandEvent& event )
{
EESCHEMA_SETTINGS::PANEL_FIELD_EDITOR& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
// Build a pop menu:
wxMenu menu;
menu.Append( 4204, _( "Include 'DNP' Symbols" ), wxEmptyString, wxITEM_CHECK );
menu.Append( 4205, _( "Include 'Exclude from BOM' Symbols" ), wxEmptyString, wxITEM_CHECK );
menu.AppendSeparator();
menu.Append( 4206, _( "Highlight on Cross Probe" ), wxEmptyString, wxITEM_CHECK );
menu.Append( 4207, _( "Select on Cross Probe" ), wxEmptyString, wxITEM_CHECK );
menu.Check( 4204, !m_dataModel->GetExcludeDNP() );
menu.Check( 4205, m_dataModel->GetIncludeExcludedFromBOM() );
menu.Check( 4206, cfg.selection_mode == 0 );
menu.Check( 4207, cfg.selection_mode == 1 );
// menu_id is the selected submenu id from the popup menu or wxID_NONE
int menu_id = m_bMenu->GetPopupMenuSelectionFromUser( menu );
if( menu_id == 0 || menu_id == 4204 )
{
m_dataModel->SetExcludeDNP( !m_dataModel->GetExcludeDNP() );
m_dataModel->RebuildRows();
m_grid->ForceRefresh();
syncBomPresetSelection();
}
else if( menu_id == 1 || menu_id == 4205 )
{
m_dataModel->SetExcludeDNP( m_dataModel->GetIncludeExcludedFromBOM() );
m_dataModel->RebuildRows();
m_grid->ForceRefresh();
syncBomPresetSelection();
}
else if( menu_id == 3 || menu_id == 4206 )
{
if( cfg.selection_mode != 0 )
cfg.selection_mode = 0;
else
cfg.selection_mode = 2;
}
else if( menu_id == 4 || menu_id == 4207 )
{
if( cfg.selection_mode != 1 )
cfg.selection_mode = 1;
else
cfg.selection_mode = 2;
}
}
2021-07-03 21:43:30 +01:00
void DIALOG_SYMBOL_FIELDS_TABLE::OnColSort( wxGridEvent& aEvent )
{
int sortCol = aEvent.GetCol();
std::string key( m_dataModel->GetColFieldName( sortCol ).ToUTF8() );
bool ascending;
// Don't sort by item number, it is generated by the sort
if( m_dataModel->ColIsItemNumber( sortCol ) )
{
aEvent.Veto();
return;
}
// This is bonkers, but wxWidgets doesn't tell us ascending/descending in the event, and
// if we ask it will give us pre-event info.
if( m_grid->IsSortingBy( sortCol ) )
{
// same column; invert ascending
ascending = !m_grid->IsSortOrderAscending();
}
else
{
// different column; start with ascending
ascending = true;
}
m_dataModel->SetSorting( sortCol, ascending );
m_dataModel->RebuildRows();
m_grid->ForceRefresh();
syncBomPresetSelection();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnColMove( wxGridEvent& aEvent )
{
int origPos = aEvent.GetCol();
// Save column widths since the setup function uses the saved config values
EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
for( int i = 0; i < m_grid->GetNumberCols(); i++ )
{
if( m_grid->IsColShown( i ) )
{
std::string fieldName( m_dataModel->GetColFieldName( i ).ToUTF8() );
cfg->m_FieldEditorPanel.field_widths[fieldName] = m_grid->GetColSize( i );
}
}
CallAfter(
[origPos, this]()
{
int newPos = m_grid->GetColPos( origPos );
#ifdef __WXMAC__
if( newPos < origPos )
newPos += 1;
#endif
m_dataModel->MoveColumn( origPos, newPos );
// "Unmove" the column since we've moved the column internally
m_grid->ResetColPos();
// We need to reset all the column attr's to the correct column order
SetupAllColumnProperties();
m_grid->ForceRefresh();
} );
syncBomPresetSelection();
}
void DIALOG_SYMBOL_FIELDS_TABLE::ShowHideColumn( int aCol, bool aShow )
{
if( aShow )
m_grid->ShowCol( aCol );
else
m_grid->HideCol( aCol );
m_dataModel->SetShowColumn( aCol, aShow );
syncBomPresetSelection();
m_grid->ForceRefresh();
OnModify();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnViewControlsCellChanged( wxGridEvent& aEvent )
{
int row = aEvent.GetRow();
wxCHECK( row < m_viewControlsGrid->GetNumberRows(), /* void */ );
switch( aEvent.GetCol() )
{
case DISPLAY_NAME_COLUMN:
{
wxString label = m_viewControlsDataModel->GetValue( row, DISPLAY_NAME_COLUMN );
wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
int dataCol = m_dataModel->GetFieldNameCol( fieldName );
if( dataCol != -1 )
{
m_dataModel->SetColLabelValue( dataCol, label );
m_grid->SetColLabelValue( dataCol, label );
syncBomPresetSelection();
m_grid->ForceRefresh();
OnModify();
}
break;
}
case SHOW_FIELD_COLUMN:
{
wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
bool value = m_viewControlsDataModel->GetValueAsBool( row, SHOW_FIELD_COLUMN );
int dataCol = m_dataModel->GetFieldNameCol( fieldName );
if( dataCol != -1 )
ShowHideColumn( dataCol, value );
break;
}
case GROUP_BY_COLUMN:
{
wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
bool value = m_viewControlsDataModel->GetValueAsBool( row, GROUP_BY_COLUMN );
int dataCol = m_dataModel->GetFieldNameCol( fieldName );
if( m_dataModel->ColIsQuantity( dataCol ) && value )
{
DisplayError( this, _( "The Quantity column cannot be grouped by." ) );
value = false;
m_viewControlsDataModel->SetValueAsBool( value, row, GROUP_BY_COLUMN );
break;
}
if( m_dataModel->ColIsItemNumber( dataCol ) && value )
{
DisplayError( this, _( "The Item Number column cannot be grouped by." ) );
value = false;
m_viewControlsDataModel->SetValueAsBool( value, row, GROUP_BY_COLUMN );
break;
}
m_dataModel->SetGroupColumn( dataCol, value );
m_dataModel->RebuildRows();
syncBomPresetSelection();
m_grid->ForceRefresh();
OnModify();
break;
}
default:
break;
}
}
2021-07-03 21:43:30 +01:00
void DIALOG_SYMBOL_FIELDS_TABLE::OnTableValueChanged( wxGridEvent& aEvent )
{
m_grid->ForceRefresh();
OnModify();
}
2021-07-03 21:43:30 +01:00
void DIALOG_SYMBOL_FIELDS_TABLE::OnTableColSize( wxGridSizeEvent& aEvent )
{
aEvent.Skip();
m_grid->ForceRefresh();
}
2021-07-03 21:43:30 +01:00
void DIALOG_SYMBOL_FIELDS_TABLE::OnRegroupSymbols( wxCommandEvent& aEvent )
{
m_dataModel->RebuildRows();
m_grid->ForceRefresh();
}
void DIALOG_SYMBOL_FIELDS_TABLE::setScope( SCOPE aScope )
{
EESCHEMA_SETTINGS::PANEL_FIELD_EDITOR& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
cfg.scope = aScope;
m_dataModel->SetPath( m_parent->GetCurrentSheet() );
m_dataModel->SetScope( aScope );
m_dataModel->RebuildRows();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnScope( wxCommandEvent& aEvent )
{
switch( aEvent.GetSelection() )
{
case 0: setScope( SCOPE::SCOPE_ALL ); break;
case 1: setScope( SCOPE::SCOPE_SHEET ); break;
case 2: setScope( SCOPE::SCOPE_SHEET_RECURSIVE ); break;
}
}
2017-11-03 09:41:48 +01:00
2021-07-03 21:43:30 +01:00
void DIALOG_SYMBOL_FIELDS_TABLE::OnTableCellClick( wxGridEvent& event )
{
if( m_dataModel->ColIsReference( event.GetCol() ) )
{
m_grid->ClearSelection();
m_dataModel->ExpandCollapseRow( event.GetRow() );
m_grid->SetGridCursor( event.GetRow(), event.GetCol() );
}
else
{
event.Skip();
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected( wxGridRangeSelectEvent& aEvent )
{
EESCHEMA_SETTINGS::PANEL_FIELD_EDITOR& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
// Cross-probing should only work in Edit page
if( m_nbPages->GetSelection() != 0 )
return;
// Multi-select can grab the rows that are expanded child refs, and also the row
// containing the list of all child refs. Make sure we add refs/symbols uniquely
std::set<SCH_REFERENCE> refs;
std::set<SCH_ITEM*> symbols;
// This handler handles selecting and deselecting
if( aEvent.Selecting() )
{
for( int i = aEvent.GetTopRow(); i <= aEvent.GetBottomRow(); i++ )
{
for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( i ) )
refs.insert( ref );
}
for( const SCH_REFERENCE& ref : refs )
symbols.insert( ref.GetSymbol() );
}
if( cfg.selection_mode == 0 )
{
SCH_EDITOR_CONTROL* editor = m_parent->GetToolManager()->GetTool<SCH_EDITOR_CONTROL>();
if( refs.size() > 0 )
{
2025-01-06 11:34:13 +00:00
// Use of full path based on UUID allows select of not yet annotated or duplicated
// symbols
wxString symbol_path = refs.begin()->GetFullPath();
// Focus only handles one item at this time
editor->FindSymbolAndItem( &symbol_path, nullptr, true, HIGHLIGHT_SYMBOL, wxEmptyString );
}
else
{
m_parent->ClearFocus();
}
}
else if( cfg.selection_mode == 1 )
{
2025-03-12 14:41:25 +00:00
SCH_SELECTION_TOOL* selTool = m_parent->GetToolManager()->GetTool<SCH_SELECTION_TOOL>();
std::vector<SCH_ITEM*> items( symbols.begin(), symbols.end() );
if( refs.size() > 0 )
selTool->SyncSelection( refs.begin()->GetSheetPath(), nullptr, items );
else
selTool->ClearSelection();
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnSizeViewControlsGrid( wxSizeEvent& event )
{
const wxString& showColLabel = m_viewControlsGrid->GetColLabelValue( SHOW_FIELD_COLUMN );
const wxString& groupByColLabel = m_viewControlsGrid->GetColLabelValue( GROUP_BY_COLUMN );
int showColWidth = KIUI::GetTextSize( showColLabel, m_viewControlsGrid ).x + COLUMN_MARGIN;
int groupByColWidth = KIUI::GetTextSize( groupByColLabel, m_viewControlsGrid ).x + COLUMN_MARGIN;
int remainingWidth = m_viewControlsGrid->GetSize().GetX() - showColWidth - groupByColWidth;
m_viewControlsGrid->SetColSize( showColWidth, SHOW_FIELD_COLUMN );
m_viewControlsGrid->SetColSize( groupByColWidth, GROUP_BY_COLUMN );
if( m_viewControlsGrid->IsColShown( DISPLAY_NAME_COLUMN ) && m_viewControlsGrid->IsColShown( LABEL_COLUMN ) )
{
m_viewControlsGrid->SetColSize( DISPLAY_NAME_COLUMN, remainingWidth / 2 );
m_viewControlsGrid->SetColSize( LABEL_COLUMN, remainingWidth - ( remainingWidth / 2 ) );
}
else if( m_viewControlsGrid->IsColShown( DISPLAY_NAME_COLUMN ) )
{
m_viewControlsGrid->SetColSize( DISPLAY_NAME_COLUMN, remainingWidth );
}
else if( m_viewControlsGrid->IsColShown( LABEL_COLUMN ) )
{
m_viewControlsGrid->SetColSize( LABEL_COLUMN, remainingWidth );
}
event.Skip();
}
2021-07-03 21:43:30 +01:00
void DIALOG_SYMBOL_FIELDS_TABLE::OnSaveAndContinue( wxCommandEvent& aEvent )
{
if( TransferDataFromWindow() )
{
m_parent->SaveProject();
ClearModify();
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnPageChanged( wxNotebookEvent& event )
{
PreviewRefresh();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnPreviewRefresh( wxCommandEvent& event )
{
PreviewRefresh();
syncBomFmtPresetSelection();
}
void DIALOG_SYMBOL_FIELDS_TABLE::PreviewRefresh()
{
m_dataModel->RebuildRows();
m_textOutput->SetValue( m_dataModel->Export( GetCurrentBomFmtSettings() ) );
}
BOM_FMT_PRESET DIALOG_SYMBOL_FIELDS_TABLE::GetCurrentBomFmtSettings()
{
BOM_FMT_PRESET current;
current.name = m_cbBomFmtPresets->GetStringSelection();
current.fieldDelimiter = m_textFieldDelimiter->GetValue();
current.stringDelimiter = m_textStringDelimiter->GetValue();
2023-03-14 20:03:57 -04:00
current.refDelimiter = m_textRefDelimiter->GetValue();
current.refRangeDelimiter = m_textRefRangeDelimiter->GetValue();
current.keepTabs = m_checkKeepTabs->GetValue();
current.keepLineBreaks = m_checkKeepLineBreaks->GetValue();
return current;
}
void DIALOG_SYMBOL_FIELDS_TABLE::ShowEditTab()
{
m_nbPages->SetSelection( 0 );
}
void DIALOG_SYMBOL_FIELDS_TABLE::ShowExportTab()
{
m_nbPages->SetSelection( 1 );
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnOutputFileBrowseClicked( wxCommandEvent& event )
{
// Build the absolute path of current output directory to preselect it in the file browser.
wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() );
path = Prj().AbsolutePath( path );
// Calculate the export filename
wxFileName fn( Prj().AbsolutePath( m_parent->Schematic().GetFileName() ) );
fn.SetExt( FILEEXT::CsvFileExtension );
wxFileDialog saveDlg( this, _( "Bill of Materials Output File" ), path, fn.GetFullName(),
FILEEXT::CsvFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
if( saveDlg.ShowModal() == wxID_CANCEL )
return;
wxFileName file = wxFileName( saveDlg.GetPath() );
wxString defaultPath = fn.GetPathWithSep();
2025-08-10 21:29:35 +01:00
if( IsOK( this, wxString::Format( _( "Do you want to use a path relative to\n'%s'?" ), defaultPath ) ) )
{
if( !file.MakeRelativeTo( defaultPath ) )
{
2025-08-10 21:29:35 +01:00
DisplayErrorMessage( this, _( "Cannot make path relative (target volume different from schematic "
"file volume)!" ) );
}
}
m_outputFileName->SetValue( file.GetFullPath() );
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnExport( wxCommandEvent& aEvent )
{
if( m_dataModel->IsEdited() )
2024-05-06 10:47:04 +01:00
{
if( OKOrCancelDialog( nullptr, _( "Unsaved data" ),
2024-05-06 10:47:04 +01:00
_( "Changes have not yet been saved. Export unsaved data?" ), "",
_( "OK" ), _( "Cancel" ) )
== wxID_CANCEL )
2024-05-06 10:47:04 +01:00
{
return;
2024-05-06 10:47:04 +01:00
}
}
// Create output directory if it does not exist (also transform it in absolute form).
// Bail if it fails.
std::function<bool( wxString* )> textResolver =
[&]( wxString* token ) -> bool
{
SCHEMATIC& schematic = m_parent->Schematic();
// Handles m_board->GetTitleBlock() *and* m_board->GetProject()
return schematic.ResolveTextVar( &schematic.CurrentSheet(), token, 0 );
};
wxString path = m_outputFileName->GetValue();
if( path.IsEmpty() )
{
2024-07-07 22:21:22 +01:00
DisplayError( this, _( "No output file specified in Export tab." ) );
return;
}
path = ExpandTextVars( path, &textResolver );
path = ExpandEnvVarSubstitutions( path, nullptr );
wxFileName outputFile = wxFileName::FileName( path );
wxString msg;
if( !EnsureFileDirectoryExists( &outputFile,
Prj().AbsolutePath( m_parent->Schematic().GetFileName() ),
&NULL_REPORTER::GetInstance() ) )
{
msg.Printf( _( "Could not open/create path '%s'." ), outputFile.GetPath() );
DisplayError( this, msg );
return;
}
wxFFile out( outputFile.GetFullPath(), "wb" );
if( !out.IsOpened() )
{
msg.Printf( _( "Could not create BOM output '%s'." ), outputFile.GetFullPath() );
DisplayError( this, msg );
return;
}
PreviewRefresh();
if( !out.Write( m_textOutput->GetValue() ) )
{
msg.Printf( _( "Could not write BOM output '%s'." ), outputFile.GetFullPath() );
DisplayError( this, msg );
return;
}
// close the file before we tell the user it's done with the info modal :workflow meme:
out.Close();
msg.Printf( _( "Wrote BOM output to '%s'" ), outputFile.GetFullPath() );
DisplayInfoMessage( this, msg );
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnCancel( wxCommandEvent& aEvent )
{
if( m_job )
EndModal( wxID_CANCEL );
else
Close();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnOk( wxCommandEvent& aEvent )
{
TransferDataFromWindow();
2025-01-06 11:34:13 +00:00
if( m_job )
{
m_job->SetConfiguredOutputPath( m_outputFileName->GetValue() );
if( m_currentBomFmtPreset )
m_job->m_bomFmtPresetName = m_currentBomFmtPreset->name;
else
m_job->m_bomFmtPresetName = wxEmptyString;
if( m_currentBomPreset )
m_job->m_bomPresetName = m_currentBomPreset->name;
else
m_job->m_bomPresetName = wxEmptyString;
BOM_FMT_PRESET fmtSettings = GetCurrentBomFmtSettings();
m_job->m_fieldDelimiter = fmtSettings.fieldDelimiter;
m_job->m_stringDelimiter = fmtSettings.stringDelimiter;
m_job->m_refDelimiter = fmtSettings.refDelimiter;
m_job->m_refRangeDelimiter = fmtSettings.refRangeDelimiter;
m_job->m_keepTabs = fmtSettings.keepTabs;
m_job->m_keepLineBreaks = fmtSettings.keepLineBreaks;
BOM_PRESET presetFields = m_dataModel->GetBomSettings();
m_job->m_sortAsc = presetFields.sortAsc;
m_job->m_excludeDNP = presetFields.excludeDNP;
m_job->m_includeExcludedFromBOM = presetFields.includeExcludedFromBOM;
m_job->m_filterString = presetFields.filterString;
m_job->m_sortField = presetFields.sortField;
m_job->m_fieldsOrdered.clear();
m_job->m_fieldsLabels.clear();
m_job->m_fieldsGroupBy.clear();
2025-01-06 11:34:13 +00:00
for( const BOM_FIELD& modelField : m_dataModel->GetFieldsOrdered() )
{
if( modelField.show )
m_job->m_fieldsOrdered.emplace_back( modelField.name );
else
m_job->m_fieldsOrdered.emplace_back( wxT( "__" ) + modelField.name );
m_job->m_fieldsLabels.emplace_back( modelField.label );
if( modelField.groupBy )
m_job->m_fieldsGroupBy.emplace_back( modelField.name );
}
EndModal( wxID_OK );
}
else
{
Close();
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnClose( wxCloseEvent& aEvent )
{
if( m_job )
{
aEvent.Skip();
return;
}
// This is a cancel, so commit quietly as we're going to throw the results away anyway.
m_grid->CommitPendingChanges( true );
if( m_dataModel->IsEdited() && aEvent.CanVeto() )
{
if( !HandleUnsavedChanges( this, _( "Save changes?" ),
2021-10-01 21:49:14 +01:00
[&]() -> bool
{
return TransferDataFromWindow();
} ) )
{
aEvent.Veto();
2018-08-11 21:46:03 +01:00
return;
}
}
2018-08-11 21:46:03 +01:00
// Stop listening to schematic events
m_parent->Schematic().RemoveListener( this );
// Save all our settings since we're really closing
savePresetsToSchematic();
m_schSettings.m_BomExportFileName = m_outputFileName->GetValue();
EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
cfg->m_FieldEditorPanel.width = GetSize().x;
cfg->m_FieldEditorPanel.height = GetSize().y;
cfg->m_FieldEditorPanel.page = m_nbPages->GetSelection();
for( int i = 0; i < m_grid->GetNumberCols(); i++ )
{
if( m_grid->IsColShown( i ) )
{
std::string fieldName( m_dataModel->GetColFieldName( i ).ToUTF8() );
cfg->m_FieldEditorPanel.field_widths[fieldName] = m_grid->GetColSize( i );
}
}
m_parent->ClearFocus();
wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_CLOSE_DIALOG_SYMBOL_FIELDS_TABLE, wxID_ANY );
2024-07-07 22:21:22 +01:00
if( wxWindow* parent = GetParent() )
wxQueueEvent( parent, evt );
}
std::vector<BOM_PRESET> DIALOG_SYMBOL_FIELDS_TABLE::GetUserBomPresets() const
{
std::vector<BOM_PRESET> ret;
for( const std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
{
if( !pair.second.readOnly )
ret.emplace_back( pair.second );
}
return ret;
}
void DIALOG_SYMBOL_FIELDS_TABLE::SetUserBomPresets( std::vector<BOM_PRESET>& aPresetList )
{
// Reset to defaults
loadDefaultBomPresets();
for( const BOM_PRESET& preset : aPresetList )
{
if( m_bomPresets.count( preset.name ) )
continue;
m_bomPresets[preset.name] = preset;
m_bomPresetMRU.Add( preset.name );
}
rebuildBomPresetsWidget();
}
void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomPreset( const wxString& aPresetName )
{
updateBomPresetSelection( aPresetName );
wxCommandEvent dummy;
onBomPresetChanged( dummy );
}
void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomPreset( const BOM_PRESET& aPreset )
{
if( m_bomPresets.count( aPreset.name ) )
m_currentBomPreset = &m_bomPresets[aPreset.name];
else
m_currentBomPreset = nullptr;
2024-01-23 12:37:02 +00:00
if( m_currentBomPreset && !m_currentBomPreset->readOnly )
m_lastSelectedBomPreset = m_currentBomPreset;
else
m_lastSelectedBomPreset = nullptr;
updateBomPresetSelection( aPreset.name );
doApplyBomPreset( aPreset );
}
void DIALOG_SYMBOL_FIELDS_TABLE::loadDefaultBomPresets()
{
m_bomPresets.clear();
m_bomPresetMRU.clear();
// Load the read-only defaults
for( const BOM_PRESET& preset : BOM_PRESET::BuiltInPresets() )
{
m_bomPresets[preset.name] = preset;
m_bomPresets[preset.name].readOnly = true;
m_bomPresetMRU.Add( preset.name );
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::rebuildBomPresetsWidget()
{
m_cbBomPresets->Clear();
int idx = 0;
int default_idx = 0;
for( const auto& [presetName, preset] : m_bomPresets )
{
m_cbBomPresets->Append( wxGetTranslation( presetName ), (void*) &preset );
if( presetName == BOM_PRESET::DefaultEditing().name )
default_idx = idx;
idx++;
}
m_cbBomPresets->Append( wxT( "---" ) );
m_cbBomPresets->Append( _( "Save preset..." ) );
m_cbBomPresets->Append( _( "Delete preset..." ) );
// At least the built-in presets should always be present
wxASSERT( !m_bomPresets.empty() );
m_cbBomPresets->SetSelection( default_idx );
m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( default_idx ) );
}
void DIALOG_SYMBOL_FIELDS_TABLE::syncBomPresetSelection()
{
BOM_PRESET current = m_dataModel->GetBomSettings();
auto it = std::find_if( m_bomPresets.begin(), m_bomPresets.end(),
2025-04-28 12:02:43 +01:00
[&]( const std::pair<const wxString, BOM_PRESET>& aPair )
{
const BOM_PRESET& preset = aPair.second;
// Check the simple settings first
if( !( preset.sortAsc == current.sortAsc
&& preset.filterString == current.filterString
&& preset.groupSymbols == current.groupSymbols
&& preset.excludeDNP == current.excludeDNP
&& preset.includeExcludedFromBOM == current.includeExcludedFromBOM ) )
{
return false;
}
// We should compare preset.name and current.name. Unfortunately current.name is
// empty because m_dataModel->GetBomSettings() does not store the .name member.
// So use sortField member as a (not very efficient) auxiliary filter.
// As a further complication, sortField can be translated in m_bomPresets list, so
// current.sortField needs to be translated.
// Probably this not efficient and error prone test should be removed (JPC).
if( preset.sortField != wxGetTranslation( current.sortField ) )
return false;
// Only compare shown or grouped fields
std::vector<BOM_FIELD> A, B;
for( const BOM_FIELD& field : preset.fieldsOrdered )
{
if( field.show || field.groupBy )
A.emplace_back( field );
}
for( const BOM_FIELD& field : current.fieldsOrdered )
{
if( field.show || field.groupBy )
B.emplace_back( field );
}
return A == B;
} );
if( it != m_bomPresets.end() )
{
// Select the right m_cbBomPresets item.
// but these items are translated if they are predefined items.
bool do_translate = it->second.readOnly;
wxString text = do_translate ? wxGetTranslation( it->first ) : it->first;
m_cbBomPresets->SetStringSelection( text );
}
else
{
m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 ); // separator
}
2025-04-28 12:02:43 +01:00
m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( m_cbBomPresets->GetSelection() ) );
}
void DIALOG_SYMBOL_FIELDS_TABLE::updateBomPresetSelection( const wxString& aName )
{
2025-04-28 12:02:43 +01:00
// Look at m_userBomPresets to know if aName is a read only preset, or a user preset.
// Read-only presets have translated names in UI, so we have to use a translated name
// in UI selection. But for a user preset name we search for the untranslated aName.
wxString ui_label = aName;
for( const auto& [presetName, preset] : m_bomPresets )
{
if( presetName == aName )
{
if( preset.readOnly == true )
ui_label = wxGetTranslation( aName );
break;
}
}
int idx = m_cbBomPresets->FindString( ui_label );
if( idx >= 0 && m_cbBomPresets->GetSelection() != idx )
{
m_cbBomPresets->SetSelection( idx );
m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( idx ) );
}
else if( idx < 0 )
{
m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 ); // separator
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::onBomPresetChanged( wxCommandEvent& aEvent )
{
int count = m_cbBomPresets->GetCount();
int index = m_cbBomPresets->GetSelection();
auto resetSelection =
[&]()
{
if( m_currentBomPreset )
m_cbBomPresets->SetStringSelection( m_currentBomPreset->name );
else
m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 );
};
if( index == count - 3 )
{
// Separator: reject the selection
resetSelection();
return;
}
else if( index == count - 2 )
{
// Save current state to new preset
wxString name;
if( m_lastSelectedBomPreset )
name = m_lastSelectedBomPreset->name;
wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
if( dlg.ShowModal() != wxID_OK )
{
resetSelection();
return;
}
name = dlg.GetValue();
bool exists = m_bomPresets.count( name );
if( !exists )
{
m_bomPresets[name] = m_dataModel->GetBomSettings();
m_bomPresets[name].readOnly = false;
m_bomPresets[name].name = name;
}
BOM_PRESET* preset = &m_bomPresets[name];
if( !exists )
{
index = m_cbBomPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
}
else if( preset->readOnly )
{
wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
_( "Error" ), wxOK | wxICON_ERROR, this );
resetSelection();
return;
}
else
{
// Ask the user if they want to overwrite the existing preset
if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
{
resetSelection();
return;
}
*preset = m_dataModel->GetBomSettings();
preset->name = name;
index = m_cbBomPresets->FindString( name );
if( m_bomPresetMRU.Index( name ) != wxNOT_FOUND )
m_bomPresetMRU.Remove( name );
}
m_currentBomPreset = preset;
m_cbBomPresets->SetSelection( index );
m_bomPresetMRU.Insert( name, 0 );
return;
}
else if( index == count - 1 )
{
// Delete a preset
wxArrayString headers;
std::vector<wxArrayString> items;
headers.Add( _( "Presets" ) );
2025-04-28 12:02:43 +01:00
for( const auto& [name, preset] : m_bomPresets )
{
2025-04-28 12:02:43 +01:00
if( !preset.readOnly )
{
wxArrayString item;
2025-04-28 12:02:43 +01:00
item.Add( name );
items.emplace_back( item );
}
}
EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
dlg.SetListLabel( _( "Select preset:" ) );
if( dlg.ShowModal() == wxID_OK )
{
wxString presetName = dlg.GetTextSelection();
int idx = m_cbBomPresets->FindString( presetName );
if( idx != wxNOT_FOUND )
{
m_bomPresets.erase( presetName );
m_cbBomPresets->Delete( idx );
m_currentBomPreset = nullptr;
}
if( m_bomPresetMRU.Index( presetName ) != wxNOT_FOUND )
m_bomPresetMRU.Remove( presetName );
}
resetSelection();
return;
}
BOM_PRESET* preset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( index ) );
m_currentBomPreset = preset;
m_lastSelectedBomPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
if( preset )
{
doApplyBomPreset( *preset );
syncBomPresetSelection();
m_currentBomPreset = preset;
if( !m_currentBomPreset->name.IsEmpty() )
{
if( m_bomPresetMRU.Index( preset->name ) != wxNOT_FOUND )
m_bomPresetMRU.Remove( preset->name );
m_bomPresetMRU.Insert( preset->name, 0 );
}
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::doApplyBomPreset( const BOM_PRESET& aPreset )
{
// Disable rebuilds while we're applying the preset otherwise we'll be
// rebuilding the model constantly while firing off wx events
m_dataModel->DisableRebuilds();
2023-03-14 20:03:57 -04:00
// Basically, we apply the BOM preset to the data model and then
// update our UI to reflect resulting the data model state, not the preset.
m_dataModel->ApplyBomPreset( aPreset );
// BOM Presets can add, but not remove, columns, so make sure the view controls
2023-03-14 20:03:57 -04:00
// grid has all of them before starting
for( int i = 0; i < m_dataModel->GetColsCount(); i++ )
{
const wxString& fieldName( m_dataModel->GetColFieldName( i ) );
bool found = false;
for( int j = 0; j < m_viewControlsDataModel->GetNumberRows(); j++ )
2023-03-14 20:03:57 -04:00
{
if( m_viewControlsDataModel->GetCanonicalFieldName( j ) == fieldName )
2023-03-14 20:03:57 -04:00
{
found = true;
break;
}
}
// Properties like label, etc. will be added in the next loop
if( !found )
AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), false, false );
2023-03-14 20:03:57 -04:00
}
2023-03-14 20:03:57 -04:00
// Sync all fields
for( int i = 0; i < m_viewControlsDataModel->GetNumberRows(); i++ )
{
const wxString& fieldName( m_viewControlsDataModel->GetCanonicalFieldName( i ) );
2023-03-14 20:03:57 -04:00
int col = m_dataModel->GetFieldNameCol( fieldName );
if( col == -1 )
2023-03-14 20:03:57 -04:00
{
wxASSERT_MSG( true, "Fields control has a field not found in the data model." );
continue;
2023-03-14 20:03:57 -04:00
}
EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
2023-03-14 20:03:57 -04:00
std::string fieldNameStr( fieldName.ToUTF8() );
// Set column labels
const wxString& label = m_dataModel->GetColLabelValue( col );
m_viewControlsDataModel->SetValue( i, LABEL_COLUMN, label );
m_grid->SetColLabelValue( col, label );
if( cfg->m_FieldEditorPanel.field_widths.count( fieldNameStr ) )
m_grid->SetColSize( col, cfg->m_FieldEditorPanel.field_widths.at( fieldNameStr ) );
// Set shown columns
2023-03-14 20:03:57 -04:00
bool show = m_dataModel->GetShowColumn( col );
m_viewControlsDataModel->SetValueAsBool( i, SHOW_FIELD_COLUMN, show );
if( show )
m_grid->ShowCol( col );
else
m_grid->HideCol( col );
// Set grouped columns
2023-03-14 20:03:57 -04:00
bool groupBy = m_dataModel->GetGroupColumn( col );
m_viewControlsDataModel->SetValueAsBool( i, GROUP_BY_COLUMN, groupBy );
}
2023-03-14 20:03:57 -04:00
m_grid->SetSortingColumn( m_dataModel->GetSortCol(), m_dataModel->GetSortAsc() );
m_groupSymbolsBox->SetValue( m_dataModel->GetGroupingEnabled() );
m_filter->ChangeValue( m_dataModel->GetFilter() );
SetupAllColumnProperties();
2023-03-14 20:03:57 -04:00
// This will rebuild all rows and columns in the model such that the order
// and labels are right, then we refresh the shown grid data to match
m_dataModel->EnableRebuilds();
m_dataModel->RebuildRows();
m_grid->ForceRefresh();
}
std::vector<BOM_FMT_PRESET> DIALOG_SYMBOL_FIELDS_TABLE::GetUserBomFmtPresets() const
{
std::vector<BOM_FMT_PRESET> ret;
2025-04-28 12:02:43 +01:00
for( const auto& [name, preset] : m_bomFmtPresets )
{
2025-04-28 12:02:43 +01:00
if( !preset.readOnly )
ret.emplace_back( preset );
}
return ret;
}
void DIALOG_SYMBOL_FIELDS_TABLE::SetUserBomFmtPresets( std::vector<BOM_FMT_PRESET>& aPresetList )
{
// Reset to defaults
loadDefaultBomFmtPresets();
for( const BOM_FMT_PRESET& preset : aPresetList )
{
if( m_bomFmtPresets.count( preset.name ) )
continue;
m_bomFmtPresets[preset.name] = preset;
m_bomFmtPresetMRU.Add( preset.name );
}
rebuildBomFmtPresetsWidget();
}
void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomFmtPreset( const wxString& aPresetName )
{
updateBomFmtPresetSelection( aPresetName );
wxCommandEvent dummy;
onBomFmtPresetChanged( dummy );
}
void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomFmtPreset( const BOM_FMT_PRESET& aPreset )
{
2025-04-28 12:02:43 +01:00
m_currentBomFmtPreset = nullptr;
m_lastSelectedBomFmtPreset = nullptr;
if( m_bomFmtPresets.count( aPreset.name ) )
m_currentBomFmtPreset = &m_bomFmtPresets[aPreset.name];
2025-04-28 12:02:43 +01:00
if( m_currentBomFmtPreset && !m_currentBomFmtPreset->readOnly )
m_lastSelectedBomFmtPreset =m_currentBomFmtPreset;
updateBomFmtPresetSelection( aPreset.name );
doApplyBomFmtPreset( aPreset );
}
void DIALOG_SYMBOL_FIELDS_TABLE::loadDefaultBomFmtPresets()
{
m_bomFmtPresets.clear();
m_bomFmtPresetMRU.clear();
// Load the read-only defaults
for( const BOM_FMT_PRESET& preset : BOM_FMT_PRESET::BuiltInPresets() )
{
m_bomFmtPresets[preset.name] = preset;
m_bomFmtPresets[preset.name].readOnly = true;
m_bomFmtPresetMRU.Add( preset.name );
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::rebuildBomFmtPresetsWidget()
{
m_cbBomFmtPresets->Clear();
int idx = 0;
int default_idx = 0;
for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
{
m_cbBomFmtPresets->Append( wxGetTranslation( pair.first ),
static_cast<void*>( &pair.second ) );
if( pair.first == BOM_FMT_PRESET::CSV().name )
default_idx = idx;
idx++;
}
m_cbBomFmtPresets->Append( wxT( "---" ) );
m_cbBomFmtPresets->Append( _( "Save preset..." ) );
m_cbBomFmtPresets->Append( _( "Delete preset..." ) );
// At least the built-in presets should always be present
wxASSERT( !m_bomFmtPresets.empty() );
m_cbBomFmtPresets->SetSelection( default_idx );
2025-04-28 12:02:43 +01:00
m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( default_idx ) );
}
void DIALOG_SYMBOL_FIELDS_TABLE::syncBomFmtPresetSelection()
{
BOM_FMT_PRESET current = GetCurrentBomFmtSettings();
auto it = std::find_if( m_bomFmtPresets.begin(), m_bomFmtPresets.end(),
[&]( const std::pair<const wxString, BOM_FMT_PRESET>& aPair )
{
return ( aPair.second.fieldDelimiter == current.fieldDelimiter
&& aPair.second.stringDelimiter == current.stringDelimiter
2023-03-14 20:03:57 -04:00
&& aPair.second.refDelimiter == current.refDelimiter
&& aPair.second.refRangeDelimiter == current.refRangeDelimiter
&& aPair.second.keepTabs == current.keepTabs
&& aPair.second.keepLineBreaks == current.keepLineBreaks );
} );
if( it != m_bomFmtPresets.end() )
{
// Select the right m_cbBomFmtPresets item.
// but these items are translated if they are predefined items.
bool do_translate = it->second.readOnly;
wxString text = do_translate ? wxGetTranslation( it->first ) : it->first;
m_cbBomFmtPresets->SetStringSelection( text );
}
else
{
m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 ); // separator
}
2025-04-28 12:02:43 +01:00
int idx = m_cbBomFmtPresets->GetSelection();
m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
}
void DIALOG_SYMBOL_FIELDS_TABLE::updateBomFmtPresetSelection( const wxString& aName )
{
// look at m_userBomFmtPresets to know if aName is a read only preset, or a user preset.
// Read only presets have translated names in UI, so we have to use a translated name in UI selection.
// But for a user preset name we should search for aName (not translated)
wxString ui_label = aName;
for( const auto& [presetName, preset] : m_bomFmtPresets )
{
if( presetName == aName )
{
if( preset.readOnly )
ui_label = wxGetTranslation( aName );
break;
}
}
int idx = m_cbBomFmtPresets->FindString( ui_label );
if( idx >= 0 && m_cbBomFmtPresets->GetSelection() != idx )
{
m_cbBomFmtPresets->SetSelection( idx );
2025-04-28 12:02:43 +01:00
m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
}
else if( idx < 0 )
{
m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 ); // separator
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::onBomFmtPresetChanged( wxCommandEvent& aEvent )
{
int count = m_cbBomFmtPresets->GetCount();
int index = m_cbBomFmtPresets->GetSelection();
auto resetSelection =
[&]()
{
if( m_currentBomFmtPreset )
m_cbBomFmtPresets->SetStringSelection( m_currentBomFmtPreset->name );
else
m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 );
};
if( index == count - 3 )
{
// Separator: reject the selection
resetSelection();
return;
}
else if( index == count - 2 )
{
// Save current state to new preset
wxString name;
if( m_lastSelectedBomFmtPreset )
name = m_lastSelectedBomFmtPreset->name;
wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
if( dlg.ShowModal() != wxID_OK )
{
resetSelection();
return;
}
name = dlg.GetValue();
bool exists = m_bomFmtPresets.count( name );
if( !exists )
{
m_bomFmtPresets[name] = GetCurrentBomFmtSettings();
m_bomFmtPresets[name].readOnly = false;
m_bomFmtPresets[name].name = name;
}
BOM_FMT_PRESET* preset = &m_bomFmtPresets[name];
if( !exists )
{
index = m_cbBomFmtPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
}
else if( preset->readOnly )
{
wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
_( "Error" ), wxOK | wxICON_ERROR, this );
resetSelection();
return;
}
else
{
// Ask the user if they want to overwrite the existing preset
if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
{
resetSelection();
return;
}
*preset = GetCurrentBomFmtSettings();
preset->name = name;
index = m_cbBomFmtPresets->FindString( name );
if( m_bomFmtPresetMRU.Index( name ) != wxNOT_FOUND )
m_bomFmtPresetMRU.Remove( name );
}
m_currentBomFmtPreset = preset;
m_cbBomFmtPresets->SetSelection( index );
m_bomFmtPresetMRU.Insert( name, 0 );
return;
}
else if( index == count - 1 )
{
// Delete a preset
wxArrayString headers;
std::vector<wxArrayString> items;
headers.Add( _( "Presets" ) );
for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
{
if( !pair.second.readOnly )
{
wxArrayString item;
item.Add( pair.first );
items.emplace_back( item );
}
}
EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
dlg.SetListLabel( _( "Select preset:" ) );
if( dlg.ShowModal() == wxID_OK )
{
wxString presetName = dlg.GetTextSelection();
int idx = m_cbBomFmtPresets->FindString( presetName );
if( idx != wxNOT_FOUND )
{
m_bomFmtPresets.erase( presetName );
m_cbBomFmtPresets->Delete( idx );
m_currentBomFmtPreset = nullptr;
}
if( m_bomFmtPresetMRU.Index( presetName ) != wxNOT_FOUND )
m_bomFmtPresetMRU.Remove( presetName );
}
resetSelection();
return;
}
2024-05-06 10:47:04 +01:00
auto* preset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( index ) );
m_currentBomFmtPreset = preset;
m_lastSelectedBomFmtPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
if( preset )
{
doApplyBomFmtPreset( *preset );
syncBomFmtPresetSelection();
m_currentBomFmtPreset = preset;
if( !m_currentBomFmtPreset->name.IsEmpty() )
{
if( m_bomFmtPresetMRU.Index( preset->name ) != wxNOT_FOUND )
m_bomFmtPresetMRU.Remove( preset->name );
m_bomFmtPresetMRU.Insert( preset->name, 0 );
}
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::doApplyBomFmtPreset( const BOM_FMT_PRESET& aPreset )
{
m_textFieldDelimiter->ChangeValue( aPreset.fieldDelimiter );
m_textStringDelimiter->ChangeValue( aPreset.stringDelimiter );
m_textRefDelimiter->ChangeValue( aPreset.refDelimiter );
m_textRefRangeDelimiter->ChangeValue( aPreset.refRangeDelimiter );
2023-03-14 20:03:57 -04:00
m_checkKeepTabs->SetValue( aPreset.keepTabs );
m_checkKeepLineBreaks->SetValue( aPreset.keepLineBreaks );
// Refresh the preview if that's the current page
if( m_nbPages->GetSelection() == 1 )
PreviewRefresh();
}
void DIALOG_SYMBOL_FIELDS_TABLE::savePresetsToSchematic()
{
bool modified = false;
// Save our BOM presets
std::vector<BOM_PRESET> presets;
2025-04-28 12:02:43 +01:00
for( const auto& [name, preset] : m_bomPresets )
{
2025-04-28 12:02:43 +01:00
if( !preset.readOnly )
presets.emplace_back( preset );
}
if( m_schSettings.m_BomPresets != presets )
{
modified = true;
m_schSettings.m_BomPresets = presets;
}
if( m_schSettings.m_BomSettings != m_dataModel->GetBomSettings() )
{
modified = true;
m_schSettings.m_BomSettings = m_dataModel->GetBomSettings();
}
// Save our BOM Format presets
std::vector<BOM_FMT_PRESET> fmts;
2025-04-28 12:02:43 +01:00
for( const auto& [name, preset] : m_bomFmtPresets )
{
2025-04-28 12:02:43 +01:00
if( !preset.readOnly )
fmts.emplace_back( preset );
}
if( m_schSettings.m_BomFmtPresets != fmts )
{
modified = true;
m_schSettings.m_BomFmtPresets = fmts;
}
if( m_schSettings.m_BomFmtSettings != GetCurrentBomFmtSettings() )
{
modified = true;
m_schSettings.m_BomFmtSettings = GetCurrentBomFmtSettings();
}
if( modified )
m_parent->OnModify();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsAdded( SCHEMATIC& aSch,
std::vector<SCH_ITEM*>& aSchItem )
{
SCH_REFERENCE_LIST allRefs;
m_parent->Schematic().Hierarchy().GetSymbols( allRefs );
for( SCH_ITEM* item : aSchItem )
{
if( item->Type() == SCH_SYMBOL_T )
{
SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
// Don't add power symbols
if( !symbol->IsMissingLibSymbol() && symbol->IsPower() )
continue;
// Add all fields again in case this symbol has a new one
for( SCH_FIELD& field : symbol->GetFields() )
AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
m_dataModel->AddReferences( getSymbolReferences( symbol, allRefs ) );
}
else if( item->Type() == SCH_SHEET_T )
{
std::set<SCH_SYMBOL*> symbols;
SCH_REFERENCE_LIST refs = getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) );
for( SCH_REFERENCE& ref : refs )
symbols.insert( ref.GetSymbol() );
for( SCH_SYMBOL* symbol : symbols )
{
// Add all fields again in case this symbol has a new one
for( SCH_FIELD& field : symbol->GetFields() )
AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
}
m_dataModel->AddReferences( refs );
}
}
DisableSelectionEvents();
m_dataModel->RebuildRows();
EnableSelectionEvents();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsRemoved( SCHEMATIC& aSch,
std::vector<SCH_ITEM*>& aSchItem )
{
for( SCH_ITEM* item : aSchItem )
{
if( item->Type() == SCH_SYMBOL_T )
m_dataModel->RemoveSymbol( *static_cast<SCH_SYMBOL*>( item ) );
else if( item->Type() == SCH_SHEET_T )
2025-04-28 12:02:43 +01:00
m_dataModel->RemoveReferences( getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) ) );
}
DisableSelectionEvents();
m_dataModel->RebuildRows();
EnableSelectionEvents();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsChanged( SCHEMATIC& aSch,
std::vector<SCH_ITEM*>& aSchItem )
{
SCH_REFERENCE_LIST allRefs;
m_parent->Schematic().Hierarchy().GetSymbols( allRefs );
for( SCH_ITEM* item : aSchItem )
{
if( item->Type() == SCH_SYMBOL_T )
{
SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
// Don't add power symbols
if( !symbol->IsMissingLibSymbol() && symbol->IsPower() )
continue;
// Add all fields again in case this symbol has a new one
for( SCH_FIELD& field : symbol->GetFields() )
AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
m_dataModel->UpdateReferences( getSymbolReferences( symbol, allRefs ) );
}
else if( item->Type() == SCH_SHEET_T )
{
std::set<SCH_SYMBOL*> symbols;
SCH_REFERENCE_LIST refs = getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) );
for( SCH_REFERENCE& ref : refs )
symbols.insert( ref.GetSymbol() );
for( SCH_SYMBOL* symbol : symbols )
{
// Add all fields again in case this symbol has a new one
for( SCH_FIELD& field : symbol->GetFields() )
AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
}
m_dataModel->UpdateReferences( refs );
}
}
DisableSelectionEvents();
m_dataModel->RebuildRows();
EnableSelectionEvents();
}
void DIALOG_SYMBOL_FIELDS_TABLE::OnSchSheetChanged( SCHEMATIC& aSch )
{
m_dataModel->SetPath( aSch.CurrentSheet() );
if( m_dataModel->GetScope() != FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_ALL )
{
DisableSelectionEvents();
m_dataModel->RebuildRows();
EnableSelectionEvents();
}
}
void DIALOG_SYMBOL_FIELDS_TABLE::EnableSelectionEvents()
{
2025-04-28 12:02:43 +01:00
m_grid->Connect( wxEVT_GRID_RANGE_SELECTED,
wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected ),
nullptr, this );
}
void DIALOG_SYMBOL_FIELDS_TABLE::DisableSelectionEvents()
{
2025-04-28 12:02:43 +01:00
m_grid->Disconnect( wxEVT_GRID_RANGE_SELECTED,
wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected ),
nullptr, this );
}
SCH_REFERENCE_LIST
DIALOG_SYMBOL_FIELDS_TABLE::getSymbolReferences( SCH_SYMBOL* aSymbol,
SCH_REFERENCE_LIST& aCachedRefs )
{
SCH_REFERENCE_LIST symbolRefs;
for( size_t i = 0; i < aCachedRefs.GetCount(); i++ )
{
SCH_REFERENCE& ref = aCachedRefs[i];
if( ref.GetSymbol() == aSymbol )
{
ref.Split(); // Figures out if we are annotated or not
symbolRefs.AddItem( ref );
}
}
return symbolRefs;
}
SCH_REFERENCE_LIST DIALOG_SYMBOL_FIELDS_TABLE::getSheetSymbolReferences( SCH_SHEET& aSheet )
{
SCH_SHEET_LIST allSheets = m_parent->Schematic().Hierarchy();
SCH_REFERENCE_LIST sheetRefs;
// We need to operate on all instances of the sheet
for( const SCH_SHEET_INSTANCE& instance : aSheet.GetInstances() )
{
// For every sheet instance we need to get the current schematic sheet
// instance that matches that particular sheet path from the root
for( SCH_SHEET_PATH& basePath : allSheets )
{
if( basePath.Path() == instance.m_Path )
{
SCH_SHEET_PATH sheetPath = basePath;
sheetPath.push_back( &aSheet );
// Create a list of all sheets in this path, starting with the path
// of the sheet that we just deleted, then all of its subsheets
SCH_SHEET_LIST subSheets;
subSheets.push_back( sheetPath );
allSheets.GetSheetsWithinPath( subSheets, sheetPath );
subSheets.GetSymbolsWithinPath( sheetRefs, sheetPath, false, false );
break;
}
}
}
for( SCH_REFERENCE& ref : sheetRefs )
ref.Split();
return sheetRefs;
}