Variant add, delete, rename, and copy operations in the Bulk Edit Symbol Fields dialog (and toolbar variant selector) modify the schematic data model but did not call OnModify(), so closing eeschema afterward would not prompt to save changes. Fixes https://gitlab.com/kicad/code/kicad/-/issues/23410
2976 lines
94 KiB
C++
2976 lines
94 KiB
C++
/*
|
|
* 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 <advanced_config.h>
|
|
#include <common.h>
|
|
#include <base_units.h>
|
|
#include <bitmaps.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>
|
|
#include <kiface_base.h>
|
|
#include <sch_edit_frame.h>
|
|
#include <widgets/wx_infobar.h>
|
|
#include <sch_reference_list.h>
|
|
#include <tools/sch_editor_control.h>
|
|
#include <kiplatform/ui.h>
|
|
#include <widgets/grid_text_button_helpers.h>
|
|
#include <widgets/grid_text_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>
|
|
#include <wx/msgdlg.h>
|
|
#include <dialogs/eda_view_switcher.h>
|
|
#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 4
|
|
#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 ) )
|
|
{
|
|
menu.Append( MYID_SELECT_FOOTPRINT, _( "Select Footprint..." ), _( "Browse for footprint" ) );
|
|
menu.AppendSeparator();
|
|
}
|
|
else if( m_dataModel->GetColFieldName( col ) == GetCanonicalFieldName( FIELD_T::DATASHEET ) )
|
|
{
|
|
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 ) :
|
|
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_addVariantButton->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
|
|
m_deleteVariantButton->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
|
|
m_renameVariantButton->SetBitmap( KiBitmapBundle( BITMAPS::small_edit ) );
|
|
m_copyVariantButton->SetBitmap( KiBitmapBundle( BITMAPS::copy ) );
|
|
|
|
m_sidebarButton->SetBitmap( KiBitmapBundle( BITMAPS::left ) );
|
|
|
|
m_viewControlsDataModel = new VIEW_CONTROLS_GRID_DATA_MODEL( true );
|
|
|
|
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 );
|
|
|
|
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() ) );
|
|
|
|
m_variantListBox->Set( parent->Schematic().GetVariantNamesForUI() );
|
|
|
|
if( !m_parent->Schematic().GetCurrentVariant().IsEmpty() )
|
|
{
|
|
int toSelect = m_variantListBox->FindString( m_parent->Schematic().GetCurrentVariant() );
|
|
|
|
if( toSelect == wxNOT_FOUND )
|
|
m_variantListBox->SetSelection( 0 );
|
|
else
|
|
m_variantListBox->SetSelection( toSelect );
|
|
}
|
|
else
|
|
{
|
|
m_variantListBox->SetSelection( 0 );
|
|
}
|
|
|
|
updateVariantButtonStates();
|
|
|
|
if( m_job )
|
|
SetTitle( m_job->GetSettingsDialogTitle() );
|
|
|
|
// 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() );
|
|
|
|
// Set the current variant for highlighting variant-specific field values
|
|
m_dataModel->SetCurrentVariant( m_parent->Schematic().GetCurrentVariant() );
|
|
|
|
SetInitialFocus( m_grid );
|
|
m_grid->ClearSelection();
|
|
|
|
SetupStandardButtons();
|
|
|
|
finishDialogSettings();
|
|
|
|
SetSize( wxSize( horizPixelsFromDU( 600 ), vertPixelsFromDU( 300 ) ) );
|
|
|
|
EESCHEMA_SETTINGS::PANEL_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
|
|
|
|
m_viewControlsGrid->ShowHideColumns( "0 1 2 3" );
|
|
|
|
CallAfter( [this, cfg]()
|
|
{
|
|
if( cfg.sidebar_collapsed )
|
|
m_splitterMainWindow->Unsplit( m_leftPanel );
|
|
else
|
|
m_splitterMainWindow->SetSashPosition( cfg.sash_pos );
|
|
|
|
setSideBarButtonLook( cfg.sidebar_collapsed );
|
|
|
|
m_splitter_left->SetSashPosition( cfg.variant_sash_pos );
|
|
} );
|
|
|
|
if( m_job )
|
|
m_outputFileName->SetValue( m_job->GetConfiguredOutputPath() );
|
|
else
|
|
m_outputFileName->SetValue( m_schSettings.m_BomExportFileName );
|
|
|
|
Center();
|
|
|
|
// Connect Events
|
|
m_grid->Bind( wxEVT_GRID_COL_SORT, &DIALOG_SYMBOL_FIELDS_TABLE::OnColSort, this );
|
|
m_grid->Bind( wxEVT_GRID_COL_MOVE, &DIALOG_SYMBOL_FIELDS_TABLE::OnColMove, this );
|
|
m_cbBomPresets->Bind( wxEVT_CHOICE, &DIALOG_SYMBOL_FIELDS_TABLE::onBomPresetChanged, this );
|
|
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();
|
|
}
|
|
}
|
|
|
|
|
|
DIALOG_SYMBOL_FIELDS_TABLE::~DIALOG_SYMBOL_FIELDS_TABLE()
|
|
{
|
|
savePresetsToSchematic();
|
|
m_schSettings.m_BomExportFileName = m_outputFileName->GetValue();
|
|
|
|
EESCHEMA_SETTINGS::PANEL_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
|
|
|
|
if( !cfg.sidebar_collapsed )
|
|
cfg.sash_pos = m_splitterMainWindow->GetSashPosition();
|
|
|
|
cfg.variant_sash_pos = m_splitter_left->GetSashPosition();
|
|
|
|
for( int i = 0; i < m_grid->GetNumberCols(); i++ )
|
|
{
|
|
if( m_grid->IsColShown( i ) )
|
|
{
|
|
std::string fieldName( m_dataModel->GetColFieldName( i ).ToUTF8() );
|
|
cfg.field_widths[fieldName] = m_grid->GetColSize( i );
|
|
}
|
|
}
|
|
|
|
// Disconnect Events
|
|
m_grid->Unbind( wxEVT_GRID_COL_SORT, &DIALOG_SYMBOL_FIELDS_TABLE::OnColSort, this );
|
|
m_grid->Unbind( wxEVT_GRID_COL_SORT, &DIALOG_SYMBOL_FIELDS_TABLE::OnColMove, this );
|
|
m_cbBomPresets->Unbind( wxEVT_CHOICE, &DIALOG_SYMBOL_FIELDS_TABLE::onBomPresetChanged, this );
|
|
m_cbBomFmtPresets->Unbind( wxEVT_CHOICE, &DIALOG_SYMBOL_FIELDS_TABLE::onBomFmtPresetChanged, this );
|
|
m_viewControlsGrid->Unbind( wxEVT_GRID_CELL_CHANGED, &DIALOG_SYMBOL_FIELDS_TABLE::OnViewControlsCellChanged, this );
|
|
|
|
// Delete the GRID_TRICKS.
|
|
m_viewControlsGrid->PopEventHandler( true );
|
|
m_grid->PopEventHandler( true );
|
|
|
|
// we gave ownership of m_viewControlsDataModel & m_dataModel to the wxGrids...
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::setSideBarButtonLook( bool aIsLeftPanelCollapsed )
|
|
{
|
|
// Set bitmap and tooltip according to left panel visibility
|
|
|
|
if( aIsLeftPanelCollapsed )
|
|
{
|
|
m_sidebarButton->SetBitmap( KiBitmapBundle( BITMAPS::right ) );
|
|
m_sidebarButton->SetToolTip( _( "Expand left panel" ) );
|
|
}
|
|
else
|
|
{
|
|
m_sidebarButton->SetBitmap( KiBitmapBundle( BITMAPS::left ) );
|
|
m_sidebarButton->SetToolTip( _( "Collapse left panel" ) );
|
|
}
|
|
}
|
|
|
|
|
|
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();
|
|
attr->SetRenderer( new GRID_CELL_TEXT_RENDERER() );
|
|
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->SetRenderer( new GRID_CELL_TEXT_RENDERER() );
|
|
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 );
|
|
}
|
|
|
|
|
|
bool DIALOG_SYMBOL_FIELDS_TABLE::TransferDataToWindow()
|
|
{
|
|
if( !wxDialog::TransferDataToWindow() )
|
|
return false;
|
|
|
|
LoadFieldNames(); // loads rows into m_viewControlsDataModel and columns into m_dataModel
|
|
|
|
// Load our BOM view presets
|
|
SetUserBomPresets( m_schSettings.m_BomPresets );
|
|
|
|
BOM_PRESET preset = m_schSettings.m_BomSettings;
|
|
|
|
if( m_job )
|
|
{
|
|
preset.name = m_job->m_bomPresetName;
|
|
preset.excludeDNP = m_job->m_excludeDNP;
|
|
preset.filterString = m_job->m_filterString;
|
|
preset.sortAsc = m_job->m_sortAsc;
|
|
preset.sortField = m_job->m_sortField;
|
|
preset.groupSymbols = m_job->m_groupSymbols;
|
|
|
|
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++;
|
|
}
|
|
}
|
|
|
|
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();
|
|
|
|
TOOL_MANAGER* toolMgr = m_parent->GetToolManager();
|
|
SCH_SELECTION_TOOL* selectionTool = toolMgr->GetTool<SCH_SELECTION_TOOL>();
|
|
SCH_SELECTION& selection = selectionTool->GetSelection();
|
|
SCH_SYMBOL* symbol = nullptr;
|
|
|
|
m_dataModel->SetGroupingEnabled( m_groupSymbolsBox->GetValue() );
|
|
|
|
wxCommandEvent dummy;
|
|
OnScope( dummy );
|
|
|
|
if( selection.GetSize() == 1 )
|
|
{
|
|
EDA_ITEM* item = selection.Front();
|
|
|
|
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;
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
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();
|
|
wxString currentVariant = m_parent->Schematic().GetCurrentVariant();
|
|
|
|
m_dataModel->ApplyData( commit, m_schSettings.m_TemplateFieldNames, currentVariant );
|
|
|
|
if( !commit.Empty() )
|
|
{
|
|
commit.Push( wxS( "Symbol Fields Table Edit" ) ); // Push clears the commit buffer.
|
|
m_parent->OnModify();
|
|
}
|
|
|
|
// Reset the view to where we left the user
|
|
m_parent->SetCurrentSheet( currentSheet );
|
|
m_parent->SyncView();
|
|
m_parent->Refresh();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
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++ )
|
|
{
|
|
if( m_viewControlsDataModel->GetCanonicalFieldName( row ) == aFieldName )
|
|
return;
|
|
}
|
|
|
|
m_dataModel->AddColumn( aFieldName, aLabelValue, addedByUser, m_parent->Schematic().GetCurrentVariant() );
|
|
|
|
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 };
|
|
} );
|
|
}
|
|
|
|
|
|
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;
|
|
|
|
for( int ii = 0; ii < (int) m_symbolsList.GetCount(); ++ii )
|
|
{
|
|
SCH_SYMBOL* symbol = m_symbolsList[ii].GetSymbol();
|
|
|
|
for( const SCH_FIELD& field : symbol->GetFields() )
|
|
{
|
|
if( !field.IsMandatory() && !field.IsPrivate() )
|
|
userFieldNames.insert( field.GetName() );
|
|
}
|
|
}
|
|
|
|
for( const wxString& fieldName : userFieldNames )
|
|
AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), true, false );
|
|
|
|
// Add any templateFieldNames which aren't already present in the userFieldNames
|
|
for( const TEMPLATE_FIELDNAME& tfn : m_schSettings.m_TemplateFieldNames.GetTemplateFieldNames() )
|
|
{
|
|
if( userFieldNames.count( tfn.m_Name ) == 0 )
|
|
AddField( tfn.m_Name, GetGeneratedFieldDisplayName( tfn.m_Name ), false, false );
|
|
}
|
|
}
|
|
|
|
|
|
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 ) )
|
|
{
|
|
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 );
|
|
|
|
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 );
|
|
wxString label = m_viewControlsDataModel->GetValue( row, LABEL_COLUMN );
|
|
bool labelIsAutogenerated = label.IsSameAs( GetGeneratedFieldDisplayName( fieldName ) );
|
|
|
|
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 )
|
|
{
|
|
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 );
|
|
|
|
if( labelIsAutogenerated )
|
|
{
|
|
m_viewControlsDataModel->SetValue( row, LABEL_COLUMN, GetGeneratedFieldDisplayName( newFieldName ) );
|
|
wxGridEvent evt( m_viewControlsGrid->GetId(), wxEVT_GRID_CELL_CHANGED, m_viewControlsGrid, row, LABEL_COLUMN );
|
|
OnViewControlsCellChanged( evt );
|
|
}
|
|
|
|
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__ ) // 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
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::setScope( 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;
|
|
}
|
|
}
|
|
|
|
|
|
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_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
|
|
|
|
// Build a pop menu:
|
|
wxMenu menu;
|
|
|
|
menu.Append( 4204, _( "Include 'DNP' Symbols" ),
|
|
_( "Show symbols marked 'DNP' in the table. This setting also controls whether or not 'DNP' "
|
|
"symbols are included on export." ),
|
|
wxITEM_CHECK );
|
|
menu.Check( 4204, !m_dataModel->GetExcludeDNP() );
|
|
|
|
menu.Append( 4205, _( "Include 'Exclude from BOM' Symbols" ),
|
|
_( "Show symbols marked 'Exclude from BOM' in the table. Symbols marked 'Exclude from BOM' "
|
|
"are never included on export." ),
|
|
wxITEM_CHECK );
|
|
menu.Check( 4205, m_dataModel->GetIncludeExcludedFromBOM() );
|
|
|
|
menu.AppendSeparator();
|
|
|
|
menu.Append( 4206, _( "Highlight on Cross-probe" ),
|
|
_( "Highlight corresponding item on canvas when it is selected in the table" ),
|
|
wxITEM_CHECK );
|
|
menu.Check( 4206, cfg.selection_mode == 0 );
|
|
|
|
menu.Append( 4207, _( "Select on Cross-probe" ),
|
|
_( "Select corresponding item on canvas when it is selected in the table" ),
|
|
wxITEM_CHECK );
|
|
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->SetIncludeExcludedFromBOM( !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;
|
|
}
|
|
}
|
|
|
|
|
|
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();
|
|
|
|
if( m_nbPages->GetSelection() == 1 )
|
|
PreviewRefresh();
|
|
else
|
|
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 LABEL_COLUMN:
|
|
{
|
|
wxString label = m_viewControlsDataModel->GetValue( row, LABEL_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 );
|
|
|
|
if( m_nbPages->GetSelection() == 1 )
|
|
PreviewRefresh();
|
|
else
|
|
m_grid->ForceRefresh();
|
|
|
|
syncBomPresetSelection();
|
|
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( row, GROUP_BY_COLUMN, value );
|
|
break;
|
|
}
|
|
|
|
if( m_dataModel->ColIsItemNumber( dataCol ) && value )
|
|
{
|
|
DisplayError( this, _( "The Item Number column cannot be grouped by." ) );
|
|
|
|
value = false;
|
|
m_viewControlsDataModel->SetValueAsBool( row, GROUP_BY_COLUMN, value );
|
|
break;
|
|
}
|
|
|
|
m_dataModel->SetGroupColumn( dataCol, value );
|
|
m_dataModel->RebuildRows();
|
|
|
|
if( m_nbPages->GetSelection() == 1 )
|
|
PreviewRefresh();
|
|
else
|
|
m_grid->ForceRefresh();
|
|
|
|
syncBomPresetSelection();
|
|
OnModify();
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::OnTableValueChanged( wxGridEvent& aEvent )
|
|
{
|
|
m_grid->ForceRefresh();
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::OnTableColSize( wxGridSizeEvent& aEvent )
|
|
{
|
|
aEvent.Skip();
|
|
|
|
m_grid->ForceRefresh();
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::OnRegroupSymbols( wxCommandEvent& aEvent )
|
|
{
|
|
m_dataModel->RebuildRows();
|
|
m_grid->ForceRefresh();
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::OnTableCellClick( wxGridEvent& event )
|
|
{
|
|
if( m_dataModel->IsExpanderColumn( 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_SYMBOL_FIELDS_TABLE& 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 )
|
|
{
|
|
// 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 )
|
|
{
|
|
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, std::max( remainingWidth / 2, 60 ) );
|
|
m_viewControlsGrid->SetColSize( LABEL_COLUMN, std::max( remainingWidth - ( remainingWidth / 2 ), 60 ) );
|
|
}
|
|
else if( m_viewControlsGrid->IsColShown( DISPLAY_NAME_COLUMN ) )
|
|
{
|
|
m_viewControlsGrid->SetColSize( DISPLAY_NAME_COLUMN, std::max( remainingWidth, 60 ) );
|
|
}
|
|
else if( m_viewControlsGrid->IsColShown( LABEL_COLUMN ) )
|
|
{
|
|
m_viewControlsGrid->SetColSize( LABEL_COLUMN, std::max( remainingWidth, 60 ) );
|
|
}
|
|
|
|
event.Skip();
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::OnSaveAndContinue( wxCommandEvent& aEvent )
|
|
{
|
|
if( TransferDataFromWindow() )
|
|
{
|
|
m_parent->SaveProject();
|
|
ClearModify();
|
|
}
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::OnPageChanged( wxNotebookEvent& event )
|
|
{
|
|
if( m_dataModel->GetColsCount() )
|
|
PreviewRefresh();
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::OnPreviewRefresh( wxCommandEvent& event )
|
|
{
|
|
PreviewRefresh();
|
|
syncBomFmtPresetSelection();
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::PreviewRefresh()
|
|
{
|
|
bool saveIncludeExcudedFromBOM = m_dataModel->GetIncludeExcludedFromBOM();
|
|
|
|
m_dataModel->SetIncludeExcludedFromBOM( false );
|
|
m_dataModel->RebuildRows();
|
|
|
|
m_textOutput->SetValue( m_dataModel->Export( GetCurrentBomFmtSettings() ) );
|
|
|
|
if( saveIncludeExcudedFromBOM )
|
|
{
|
|
m_dataModel->SetIncludeExcludedFromBOM( true );
|
|
m_dataModel->RebuildRows();
|
|
}
|
|
}
|
|
|
|
|
|
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();
|
|
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 );
|
|
|
|
KIPLATFORM::UI::AllowNetworkFileSystems( &saveDlg );
|
|
|
|
if( saveDlg.ShowModal() == wxID_CANCEL )
|
|
return;
|
|
|
|
|
|
wxFileName file = wxFileName( saveDlg.GetPath() );
|
|
wxString defaultPath = fn.GetPathWithSep();
|
|
|
|
if( IsOK( this, wxString::Format( _( "Do you want to use a path relative to\n'%s'?" ), defaultPath ) ) )
|
|
{
|
|
if( !file.MakeRelativeTo( defaultPath ) )
|
|
{
|
|
DisplayErrorMessage( this, _( "Cannot make path relative (target volume different from schematic "
|
|
"file volume)!" ) );
|
|
}
|
|
}
|
|
|
|
m_outputFileName->SetValue( file.GetFullPath() );
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::OnSidebarToggle( wxCommandEvent& event )
|
|
{
|
|
EESCHEMA_SETTINGS::PANEL_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
|
|
|
|
if( cfg.sidebar_collapsed )
|
|
{
|
|
cfg.sidebar_collapsed = false;
|
|
m_splitterMainWindow->SplitVertically( m_leftPanel, m_rightPanel, cfg.sash_pos );
|
|
}
|
|
else
|
|
{
|
|
cfg.sash_pos = m_splitterMainWindow->GetSashPosition();
|
|
|
|
cfg.sidebar_collapsed = true;
|
|
m_splitterMainWindow->Unsplit( m_leftPanel );
|
|
}
|
|
|
|
setSideBarButtonLook( cfg.sidebar_collapsed );
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::OnExport( wxCommandEvent& aEvent )
|
|
{
|
|
if( m_dataModel->IsEdited() )
|
|
{
|
|
if( OKOrCancelDialog( nullptr, _( "Unsaved data" ),
|
|
_( "Changes have not yet been saved. Export unsaved data?" ), "",
|
|
_( "OK" ), _( "Cancel" ) )
|
|
== wxID_CANCEL )
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 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() )
|
|
{
|
|
DisplayError( this, _( "No output file specified in Export tab." ) );
|
|
return;
|
|
}
|
|
|
|
path = ExpandTextVars( path, &textResolver );
|
|
path = ExpandEnvVarSubstitutions( path, &Prj() );
|
|
|
|
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();
|
|
|
|
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_filterString = presetFields.filterString;
|
|
m_job->m_sortField = presetFields.sortField;
|
|
m_job->m_groupSymbols = presetFields.groupSymbols;
|
|
|
|
m_job->m_fieldsOrdered.clear();
|
|
m_job->m_fieldsLabels.clear();
|
|
m_job->m_fieldsGroupBy.clear();
|
|
|
|
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 );
|
|
}
|
|
|
|
wxString selectedVariant = getSelectedVariant();
|
|
|
|
if( !selectedVariant.IsEmpty() )
|
|
m_job->m_variantNames.push_back( selectedVariant );
|
|
|
|
EndModal( wxID_OK );
|
|
}
|
|
else
|
|
{
|
|
Close();
|
|
}
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::OnClose( wxCloseEvent& aEvent )
|
|
{
|
|
if( m_job )
|
|
{
|
|
aEvent.Skip();
|
|
return;
|
|
}
|
|
|
|
m_grid->CommitPendingChanges( true );
|
|
|
|
if( m_dataModel->IsEdited() && aEvent.CanVeto() )
|
|
{
|
|
if( !HandleUnsavedChanges( this, _( "Save changes?" ),
|
|
[&]() -> bool
|
|
{
|
|
return TransferDataFromWindow();
|
|
} ) )
|
|
{
|
|
aEvent.Veto();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Stop listening to schematic events
|
|
m_parent->Schematic().RemoveListener( this );
|
|
m_parent->ClearFocus();
|
|
|
|
wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_CLOSE_DIALOG_SYMBOL_FIELDS_TABLE, wxID_ANY );
|
|
|
|
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;
|
|
|
|
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(),
|
|
[&]( 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
|
|
}
|
|
|
|
m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( m_cbBomPresets->GetSelection() ) );
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::updateBomPresetSelection( const wxString& aName )
|
|
{
|
|
// 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" ) );
|
|
|
|
for( const auto& [name, preset] : m_bomPresets )
|
|
{
|
|
if( !preset.readOnly )
|
|
{
|
|
wxArrayString item;
|
|
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();
|
|
|
|
// 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, m_parent->Schematic().GetCurrentVariant() );
|
|
|
|
// BOM Presets can add, but not remove, columns, so make sure the view controls
|
|
// 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++ )
|
|
{
|
|
if( m_viewControlsDataModel->GetCanonicalFieldName( j ) == fieldName )
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Properties like label, etc. will be added in the next loop
|
|
if( !found )
|
|
AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), false, false );
|
|
}
|
|
|
|
// Sync all fields
|
|
for( int i = 0; i < m_viewControlsDataModel->GetNumberRows(); i++ )
|
|
{
|
|
const wxString& fieldName( m_viewControlsDataModel->GetCanonicalFieldName( i ) );
|
|
int col = m_dataModel->GetFieldNameCol( fieldName );
|
|
|
|
if( col == -1 )
|
|
{
|
|
wxASSERT_MSG( true, "Fields control has a field not found in the data model." );
|
|
continue;
|
|
}
|
|
|
|
EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
|
|
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
|
|
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
|
|
bool groupBy = m_dataModel->GetGroupColumn( col );
|
|
m_viewControlsDataModel->SetValueAsBool( i, GROUP_BY_COLUMN, groupBy );
|
|
}
|
|
|
|
m_grid->SetSortingColumn( m_dataModel->GetSortCol(), m_dataModel->GetSortAsc() );
|
|
m_groupSymbolsBox->SetValue( m_dataModel->GetGroupingEnabled() );
|
|
m_filter->ChangeValue( m_dataModel->GetFilter() );
|
|
|
|
SetupAllColumnProperties();
|
|
|
|
// 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();
|
|
|
|
if( m_nbPages->GetSelection() == 1 )
|
|
PreviewRefresh();
|
|
else
|
|
m_grid->ForceRefresh();
|
|
}
|
|
|
|
|
|
std::vector<BOM_FMT_PRESET> DIALOG_SYMBOL_FIELDS_TABLE::GetUserBomFmtPresets() const
|
|
{
|
|
std::vector<BOM_FMT_PRESET> ret;
|
|
|
|
for( const auto& [name, preset] : m_bomFmtPresets )
|
|
{
|
|
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 )
|
|
{
|
|
m_currentBomFmtPreset = nullptr;
|
|
m_lastSelectedBomFmtPreset = nullptr;
|
|
|
|
if( m_bomFmtPresets.count( aPreset.name ) )
|
|
m_currentBomFmtPreset = &m_bomFmtPresets[aPreset.name];
|
|
|
|
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( const auto& [presetName, preset] : m_bomFmtPresets )
|
|
{
|
|
m_cbBomFmtPresets->Append( wxGetTranslation( presetName ), (void*) &preset );
|
|
|
|
if( presetName == 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 );
|
|
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
|
|
&& 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
|
|
}
|
|
|
|
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 );
|
|
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;
|
|
}
|
|
|
|
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 );
|
|
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;
|
|
|
|
for( const auto& [name, preset] : m_bomPresets )
|
|
{
|
|
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() && !m_job )
|
|
{
|
|
modified = true;
|
|
m_schSettings.m_BomSettings = m_dataModel->GetBomSettings();
|
|
}
|
|
|
|
// Save our BOM Format presets
|
|
std::vector<BOM_FMT_PRESET> fmts;
|
|
|
|
for( const auto& [name, preset] : m_bomFmtPresets )
|
|
{
|
|
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() && !m_job )
|
|
{
|
|
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 )
|
|
{
|
|
std::set<wxString> savedSelection = SaveGridSelection();
|
|
|
|
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();
|
|
RestoreGridSelection( savedSelection );
|
|
EnableSelectionEvents();
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsRemoved( SCHEMATIC& aSch, std::vector<SCH_ITEM*>& aSchItem )
|
|
{
|
|
std::set<wxString> savedSelection = SaveGridSelection();
|
|
|
|
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 )
|
|
m_dataModel->RemoveReferences( getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) ) );
|
|
}
|
|
|
|
DisableSelectionEvents();
|
|
m_dataModel->RebuildRows();
|
|
RestoreGridSelection( savedSelection );
|
|
EnableSelectionEvents();
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsChanged( SCHEMATIC& aSch, std::vector<SCH_ITEM*>& aSchItem )
|
|
{
|
|
std::set<wxString> savedSelection = SaveGridSelection();
|
|
|
|
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 ),
|
|
m_parent->Schematic().GetCurrentVariant() );
|
|
}
|
|
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, m_parent->Schematic().GetCurrentVariant() );
|
|
}
|
|
}
|
|
|
|
DisableSelectionEvents();
|
|
m_dataModel->RebuildRows();
|
|
RestoreGridSelection( savedSelection );
|
|
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 )
|
|
{
|
|
std::set<wxString> savedSelection = SaveGridSelection();
|
|
|
|
DisableSelectionEvents();
|
|
m_dataModel->RebuildRows();
|
|
RestoreGridSelection( savedSelection );
|
|
EnableSelectionEvents();
|
|
}
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::EnableSelectionEvents()
|
|
{
|
|
m_grid->Connect( wxEVT_GRID_RANGE_SELECTED,
|
|
wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected ),
|
|
nullptr, this );
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::DisableSelectionEvents()
|
|
{
|
|
m_grid->Disconnect( wxEVT_GRID_RANGE_SELECTED,
|
|
wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected ),
|
|
nullptr, this );
|
|
}
|
|
|
|
|
|
std::set<wxString> DIALOG_SYMBOL_FIELDS_TABLE::SaveGridSelection()
|
|
{
|
|
std::set<wxString> selectedFullPaths;
|
|
|
|
wxGridCellCoordsArray topLeft = m_grid->GetSelectionBlockTopLeft();
|
|
wxGridCellCoordsArray bottomRight = m_grid->GetSelectionBlockBottomRight();
|
|
|
|
for( size_t i = 0; i < topLeft.size(); ++i )
|
|
{
|
|
for( int row = topLeft[i].GetRow(); row <= bottomRight[i].GetRow(); ++row )
|
|
{
|
|
for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( row ) )
|
|
selectedFullPaths.insert( ref.GetFullPath() );
|
|
}
|
|
}
|
|
|
|
wxArrayInt selectedRows = m_grid->GetSelectedRows();
|
|
|
|
for( int row : selectedRows )
|
|
{
|
|
for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( row ) )
|
|
selectedFullPaths.insert( ref.GetFullPath() );
|
|
}
|
|
|
|
int cursorRow = m_grid->GetGridCursorRow();
|
|
|
|
if( cursorRow >= 0 && selectedFullPaths.empty() )
|
|
{
|
|
for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( cursorRow ) )
|
|
selectedFullPaths.insert( ref.GetFullPath() );
|
|
}
|
|
|
|
return selectedFullPaths;
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::RestoreGridSelection( const std::set<wxString>& aFullPaths )
|
|
{
|
|
if( aFullPaths.empty() )
|
|
return;
|
|
|
|
m_grid->ClearSelection();
|
|
|
|
bool firstSelection = true;
|
|
|
|
for( int row = 0; row < m_dataModel->GetNumberRows(); ++row )
|
|
{
|
|
std::vector<SCH_REFERENCE> refs = m_dataModel->GetRowReferences( row );
|
|
|
|
for( const SCH_REFERENCE& ref : refs )
|
|
{
|
|
if( aFullPaths.count( ref.GetFullPath() ) )
|
|
{
|
|
m_grid->SelectRow( row, true );
|
|
|
|
if( firstSelection )
|
|
{
|
|
m_grid->SetGridCursor( row, m_grid->GetGridCursorCol() );
|
|
firstSelection = false;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::onAddVariant( wxCommandEvent& aEvent )
|
|
{
|
|
if( !m_parent->ShowAddVariantDialog() )
|
|
return;
|
|
|
|
wxArrayString ctrlContents;
|
|
ctrlContents.Add( GetDefaultVariantName() );
|
|
|
|
for( const wxString& variant : m_parent->Schematic().GetVariantNames() )
|
|
ctrlContents.Add( variant );
|
|
|
|
ctrlContents.Sort( SortVariantNames );
|
|
m_variantListBox->Set( ctrlContents );
|
|
|
|
wxString currentVariant = m_parent->Schematic().GetCurrentVariant();
|
|
int newSelection = m_variantListBox->FindString(
|
|
currentVariant.IsEmpty() ? GetDefaultVariantName() : currentVariant );
|
|
|
|
if( newSelection != wxNOT_FOUND )
|
|
m_variantListBox->SetSelection( newSelection );
|
|
|
|
updateVariantButtonStates();
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::onDeleteVariant( wxCommandEvent& aEvent )
|
|
{
|
|
int selection = m_variantListBox->GetSelection();
|
|
|
|
// An empty or default selection cannot be deleted.
|
|
if( ( selection == wxNOT_FOUND ) || ( selection == 0 ) )
|
|
{
|
|
m_parent->GetInfoBar()->ShowMessageFor( _( "Cannot delete the default variant." ),
|
|
10000, wxICON_ERROR );
|
|
return;
|
|
}
|
|
|
|
wxString variantName = m_variantListBox->GetString( selection );
|
|
m_variantListBox->Delete( selection );
|
|
m_parent->Schematic().DeleteVariant( variantName );
|
|
m_parent->OnModify();
|
|
|
|
int newSelection = std::max( 0, selection - 1 );
|
|
m_variantListBox->SetSelection( newSelection );
|
|
|
|
wxString selectedVariant = getSelectedVariant();
|
|
m_parent->SetCurrentVariant( selectedVariant );
|
|
|
|
if( m_grid->CommitPendingChanges( true ) )
|
|
{
|
|
m_dataModel->SetCurrentVariant( selectedVariant );
|
|
m_dataModel->UpdateReferences( m_dataModel->GetReferenceList(), selectedVariant );
|
|
m_dataModel->RebuildRows();
|
|
|
|
if( m_nbPages->GetSelection() == 1 )
|
|
PreviewRefresh();
|
|
else
|
|
m_grid->ForceRefresh();
|
|
}
|
|
|
|
updateVariantButtonStates();
|
|
m_parent->UpdateVariantSelectionCtrl( m_parent->Schematic().GetVariantNamesForUI() );
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::onRenameVariant( wxCommandEvent& aEvent )
|
|
{
|
|
int selection = m_variantListBox->GetSelection();
|
|
|
|
// An empty or default selection cannot be renamed.
|
|
if( ( selection == wxNOT_FOUND ) || ( selection == 0 ) )
|
|
{
|
|
m_parent->GetInfoBar()->ShowMessageFor( _( "Cannot rename the default variant." ),
|
|
10000, wxICON_ERROR );
|
|
return;
|
|
}
|
|
|
|
wxString oldVariantName = m_variantListBox->GetString( selection );
|
|
|
|
wxTextEntryDialog dlg( this, _( "Enter new variant name:" ), _( "Rename Design Variant" ),
|
|
oldVariantName, wxOK | wxCANCEL | wxCENTER );
|
|
|
|
if( dlg.ShowModal() == wxID_CANCEL )
|
|
return;
|
|
|
|
wxString newVariantName = dlg.GetValue().Trim().Trim( false );
|
|
|
|
// Empty name is not allowed.
|
|
if( newVariantName.IsEmpty() )
|
|
{
|
|
m_parent->GetInfoBar()->ShowMessageFor( _( "Variant name cannot be empty." ), 10000, wxICON_ERROR );
|
|
return;
|
|
}
|
|
|
|
// Reserved name is not allowed (case-insensitive).
|
|
if( newVariantName.CmpNoCase( GetDefaultVariantName() ) == 0 )
|
|
{
|
|
m_parent->GetInfoBar()->ShowMessageFor( wxString::Format( _( "'%s' is a reserved variant name." ),
|
|
GetDefaultVariantName() ),
|
|
10000, wxICON_ERROR );
|
|
return;
|
|
}
|
|
|
|
// Same name (exact match) - nothing to do
|
|
if( newVariantName == oldVariantName )
|
|
return;
|
|
|
|
// Duplicate name is not allowed (case-insensitive).
|
|
for( const wxString& existingName : m_parent->Schematic().GetVariantNames() )
|
|
{
|
|
if( existingName.CmpNoCase( newVariantName ) == 0
|
|
&& existingName.CmpNoCase( oldVariantName ) != 0 )
|
|
{
|
|
m_parent->GetInfoBar()->ShowMessageFor( wxString::Format( _( "Variant '%s' already exists." ),
|
|
existingName ),
|
|
0000, wxICON_ERROR );
|
|
return;
|
|
}
|
|
}
|
|
|
|
m_parent->Schematic().RenameVariant( oldVariantName, newVariantName );
|
|
m_parent->OnModify();
|
|
|
|
wxArrayString ctrlContents = m_variantListBox->GetStrings();
|
|
ctrlContents.Remove( oldVariantName );
|
|
ctrlContents.Add( newVariantName );
|
|
ctrlContents.Sort( SortVariantNames );
|
|
m_variantListBox->Set( ctrlContents );
|
|
|
|
int newSelection = m_variantListBox->FindString( newVariantName );
|
|
|
|
if( newSelection != wxNOT_FOUND )
|
|
m_variantListBox->SetSelection( newSelection );
|
|
|
|
updateVariantButtonStates();
|
|
m_parent->UpdateVariantSelectionCtrl( m_parent->Schematic().GetVariantNamesForUI() );
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::onCopyVariant( wxCommandEvent& aEvent )
|
|
{
|
|
int selection = m_variantListBox->GetSelection();
|
|
|
|
// An empty or default selection cannot be copied.
|
|
if( ( selection == wxNOT_FOUND ) || ( selection == 0 ) )
|
|
{
|
|
m_parent->GetInfoBar()->ShowMessageFor( _( "Cannot copy the default variant." ),
|
|
10000, wxICON_ERROR );
|
|
return;
|
|
}
|
|
|
|
wxString sourceVariantName = m_variantListBox->GetString( selection );
|
|
|
|
wxTextEntryDialog dlg( this, _( "Enter name for the copied variant:" ), _( "Copy Design Variant" ),
|
|
sourceVariantName + wxS( "_copy" ), wxOK | wxCANCEL | wxCENTER );
|
|
|
|
if( dlg.ShowModal() == wxID_CANCEL )
|
|
return;
|
|
|
|
wxString newVariantName = dlg.GetValue().Trim().Trim( false );
|
|
|
|
// Empty name is not allowed.
|
|
if( newVariantName.IsEmpty() )
|
|
{
|
|
m_parent->GetInfoBar()->ShowMessageFor( _( "Variant name cannot be empty." ), 10000, wxICON_ERROR );
|
|
return;
|
|
}
|
|
|
|
// Duplicate name is not allowed.
|
|
if( m_variantListBox->FindString( newVariantName ) != wxNOT_FOUND )
|
|
{
|
|
m_parent->GetInfoBar()->ShowMessageFor( wxString::Format( _( "Variant '%s' already exists." ),
|
|
newVariantName ),
|
|
10000, wxICON_ERROR );
|
|
return;
|
|
}
|
|
|
|
m_parent->Schematic().CopyVariant( sourceVariantName, newVariantName );
|
|
m_parent->OnModify();
|
|
|
|
wxArrayString ctrlContents = m_variantListBox->GetStrings();
|
|
ctrlContents.Add( newVariantName );
|
|
ctrlContents.Sort( SortVariantNames );
|
|
m_variantListBox->Set( ctrlContents );
|
|
|
|
int newSelection = m_variantListBox->FindString( newVariantName );
|
|
|
|
if( newSelection != wxNOT_FOUND )
|
|
m_variantListBox->SetSelection( newSelection );
|
|
|
|
updateVariantButtonStates();
|
|
m_parent->UpdateVariantSelectionCtrl( m_parent->Schematic().GetVariantNamesForUI() );
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::onVariantSelectionChange( wxCommandEvent& aEvent )
|
|
{
|
|
wxString currentVariant;
|
|
wxString selectedVariant = getSelectedVariant();
|
|
|
|
updateVariantButtonStates();
|
|
|
|
if( m_parent )
|
|
{
|
|
currentVariant = m_parent->Schematic().GetCurrentVariant();
|
|
|
|
if( currentVariant != selectedVariant )
|
|
m_parent->SetCurrentVariant( selectedVariant );
|
|
}
|
|
|
|
if( currentVariant != selectedVariant )
|
|
{
|
|
m_grid->CommitPendingChanges( true );
|
|
|
|
SCH_COMMIT commit( m_parent );
|
|
|
|
m_dataModel->ApplyData( commit, m_schSettings.m_TemplateFieldNames, currentVariant );
|
|
|
|
if( !commit.Empty() )
|
|
{
|
|
commit.Push( wxS( "Symbol Fields Table Edit" ) ); // Push clears the commit buffer.
|
|
m_parent->OnModify();
|
|
}
|
|
|
|
// Update the data model's current variant for field highlighting
|
|
m_dataModel->SetCurrentVariant( selectedVariant );
|
|
m_dataModel->UpdateReferences( m_dataModel->GetReferenceList(), selectedVariant );
|
|
m_dataModel->RebuildRows();
|
|
|
|
if( m_nbPages->GetSelection() == 1 )
|
|
PreviewRefresh();
|
|
else
|
|
m_grid->ForceRefresh();
|
|
|
|
syncBomFmtPresetSelection();
|
|
}
|
|
}
|
|
|
|
|
|
void DIALOG_SYMBOL_FIELDS_TABLE::updateVariantButtonStates()
|
|
{
|
|
int selection = m_variantListBox->GetSelection();
|
|
|
|
// Copy, rename, and delete are only enabled for non-default variant selections
|
|
bool canModify = ( selection != wxNOT_FOUND ) && ( selection != 0 );
|
|
|
|
m_copyVariantButton->Enable( canModify );
|
|
m_renameVariantButton->Enable( canModify );
|
|
m_deleteVariantButton->Enable( canModify );
|
|
}
|
|
|
|
|
|
wxString DIALOG_SYMBOL_FIELDS_TABLE::getSelectedVariant() const
|
|
{
|
|
wxString retv;
|
|
|
|
int selection = m_variantListBox->GetSelection();
|
|
|
|
if( ( selection == wxNOT_FOUND ) || ( m_variantListBox->GetString( selection ) == GetDefaultVariantName() ) )
|
|
return retv;
|
|
|
|
return m_variantListBox->GetString( selection );
|
|
}
|