KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_symbol_fields_table.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2017 Oliver Walters
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, you may find one here:
19 * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
20 * or you may search the http://www.gnu.org website for the version 2 license,
21 * or you may write to the Free Software Foundation, Inc.,
22 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
23 */
24
25#include <advanced_config.h>
26#include <common.h>
27#include <base_units.h>
28#include <bitmaps.h>
29#include <confirm.h>
30#include <eda_doc.h>
32#include <schematic_settings.h>
33#include <general.h>
34#include <grid_tricks.h>
35#include <string_utils.h>
36#include <kiface_base.h>
37#include <sch_edit_frame.h>
38#include <widgets/wx_infobar.h>
39#include <sch_reference_list.h>
41#include <kiplatform/ui.h>
46#include <widgets/wx_grid.h>
48#include <wx/debug.h>
49#include <wx/ffile.h>
50#include <wx/grid.h>
51#include <wx/textdlg.h>
52#include <wx/filedlg.h>
53#include <wx/msgdlg.h>
56#include <fields_data_model.h>
57#include <eda_list_dialog.h>
58#include <project_sch.h>
60
61wxDEFINE_EVENT( EDA_EVT_CLOSE_DIALOG_SYMBOL_FIELDS_TABLE, wxCommandEvent );
62
63#ifdef __WXMAC__
64#define COLUMN_MARGIN 4
65#else
66#define COLUMN_MARGIN 15
67#endif
68
70
71
72enum
73{
76};
77
79{
80public:
82 GRID_TRICKS( aGrid )
83 {}
84
85protected:
86 void doPopupSelection( wxCommandEvent& event ) override
87 {
88 if( event.GetId() >= GRIDTRICKS_FIRST_SHOWHIDE )
89 m_grid->PostSizeEvent();
90
92 }
93};
94
95
97{
98public:
100 VIEW_CONTROLS_GRID_DATA_MODEL* aViewFieldsData,
101 FIELDS_EDITOR_GRID_DATA_MODEL* aDataModel, EMBEDDED_FILES* aFiles ) :
102 GRID_TRICKS( aGrid ),
103 m_dlg( aParent ),
104 m_viewControlsDataModel( aViewFieldsData ),
105 m_dataModel( aDataModel ),
106 m_files( aFiles )
107 {}
108
109protected:
110 void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override
111 {
112 int col = m_grid->GetGridCursorCol();
113
114 if( m_dataModel->GetColFieldName( col ) == GetCanonicalFieldName( FIELD_T::FOOTPRINT ) )
115 {
116 menu.Append( MYID_SELECT_FOOTPRINT, _( "Select Footprint..." ), _( "Browse for footprint" ) );
117 menu.AppendSeparator();
118 }
119 else if( m_dataModel->GetColFieldName( col ) == GetCanonicalFieldName( FIELD_T::DATASHEET ) )
120 {
121 menu.Append( MYID_SHOW_DATASHEET, _( "Show Datasheet" ), _( "Show datasheet in browser" ) );
122 menu.AppendSeparator();
123 }
124
125 GRID_TRICKS::showPopupMenu( menu, aEvent );
126 }
127
128 void doPopupSelection( wxCommandEvent& event ) override
129 {
130 int row = m_grid->GetGridCursorRow();
131 int col = m_grid->GetGridCursorCol();
132
133 if( event.GetId() == MYID_SELECT_FOOTPRINT )
134 {
135 // pick a footprint using the footprint picker.
136 wxString fpid = m_grid->GetCellValue( row, col );
137
138 if( KIWAY_PLAYER* frame = m_dlg->Kiway().Player( FRAME_FOOTPRINT_CHOOSER, true, m_dlg ) )
139 {
140 if( frame->ShowModal( &fpid, m_dlg ) )
141 m_grid->SetCellValue( row, col, fpid );
142
143 frame->Destroy();
144 }
145 }
146 else if (event.GetId() == MYID_SHOW_DATASHEET )
147 {
148 wxString datasheet_uri = m_grid->GetCellValue( row, col );
149 GetAssociatedDocument( m_dlg, datasheet_uri, &m_dlg->Prj(), PROJECT_SCH::SchSearchS( &m_dlg->Prj() ),
150 { m_files } );
151 }
152 else if( event.GetId() >= GRIDTRICKS_FIRST_SHOWHIDE )
153 {
154 if( !m_grid->CommitPendingChanges( false ) )
155 return;
156
157 // Pop-up column order is the order of the shown fields, not the viewControls order
158 col = event.GetId() - GRIDTRICKS_FIRST_SHOWHIDE;
159
160 bool show = !m_dataModel->GetShowColumn( col );
161
162 m_dlg->ShowHideColumn( col, show );
163
164 wxString fieldName = m_dataModel->GetColFieldName( col );
165
166 for( row = 0; row < m_viewControlsDataModel->GetNumberRows(); row++ )
167 {
168 if( m_viewControlsDataModel->GetCanonicalFieldName( row ) == fieldName )
169 m_viewControlsDataModel->SetValueAsBool( row, SHOW_FIELD_COLUMN, show );
170 }
171
172 if( m_viewControlsDataModel->GetView() )
173 m_viewControlsDataModel->GetView()->ForceRefresh();
174 }
175 else
176 {
178 }
179 }
180
181private:
186};
187
188
191 m_currentBomPreset( nullptr ),
192 m_lastSelectedBomPreset( nullptr ),
193 m_parent( parent ),
194 m_viewControlsDataModel( nullptr ),
195 m_dataModel( nullptr ),
196 m_schSettings( parent->Schematic().Settings() ),
197 m_job( aJob )
198{
199 // Get all symbols from the list of schematic sheets
200 m_parent->Schematic().Hierarchy().GetSymbols( m_symbolsList, false );
201
203 m_bMenu->SetBitmap( KiBitmapBundle( BITMAPS::config ) );
206
210
215
217
219
220 m_viewControlsGrid->UseNativeColHeader( true );
222
223 // must be done after SetTable(), which appears to re-set it
224 m_viewControlsGrid->SetSelectionMode( wxGrid::wxGridSelectCells );
225
226 // add Cut, Copy, and Paste to wxGrid
228
229 wxGridCellAttr* attr = new wxGridCellAttr;
230 attr->SetReadOnly( true );
231 m_viewControlsDataModel->SetColAttr( attr, DISPLAY_NAME_COLUMN );
232
233 attr = new wxGridCellAttr;
234 attr->SetRenderer( new wxGridCellBoolRenderer() );
235 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
236 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
237 m_viewControlsDataModel->SetColAttr( attr, SHOW_FIELD_COLUMN );
238
239 attr = new wxGridCellAttr;
240 attr->SetRenderer( new wxGridCellBoolRenderer() );
241 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
242 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
243 m_viewControlsDataModel->SetColAttr( attr, GROUP_BY_COLUMN );
244
245 // Compress the view controls grid. (We want it to look different from the fields grid.)
246 m_viewControlsGrid->SetDefaultRowSize( m_viewControlsGrid->GetDefaultRowSize() - FromDIP( 4 ) );
247
248 m_filter->SetDescriptiveText( _( "Filter" ) );
249
250 attr = new wxGridCellAttr;
251 attr->SetEditor( new GRID_CELL_URL_EDITOR( this, PROJECT_SCH::SchSearchS( &Prj() ), { &m_parent->Schematic() } ) );
253
254 m_grid->UseNativeColHeader( true );
255 m_grid->SetTable( m_dataModel, true );
256
257 // must be done after SetTable(), which appears to re-set it
258 m_grid->SetSelectionMode( wxGrid::wxGridSelectCells );
259
260 // add Cut, Copy, and Paste to wxGrid
262 &m_parent->Schematic() ) );
263
265
266 if( !m_parent->Schematic().GetCurrentVariant().IsEmpty() )
267 {
268 int toSelect = m_variantListBox->FindString( m_parent->Schematic().GetCurrentVariant() );
269
270 if( toSelect == wxNOT_FOUND )
271 m_variantListBox->SetSelection( 0 );
272 else
273 m_variantListBox->SetSelection( toSelect );
274 }
275 else
276 {
277 m_variantListBox->SetSelection( 0 );
278 }
279
281
282 if( m_job )
283 SetTitle( m_job->GetSettingsDialogTitle() );
284
285 // DIALOG_SHIM needs a unique hash_key because classname will be the same for both job and
286 // non-job versions (which have different sizes).
287 m_hash_key = TO_UTF8( GetTitle() );
288
289 // Set the current variant for highlighting variant-specific field values
290 m_dataModel->SetCurrentVariant( m_parent->Schematic().GetCurrentVariant() );
291
293 m_grid->ClearSelection();
294
296
298
299 SetSize( wxSize( horizPixelsFromDU( 600 ), vertPixelsFromDU( 300 ) ) );
300
301 EESCHEMA_SETTINGS::PANEL_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
302
303 m_viewControlsGrid->ShowHideColumns( "0 1 2 3" );
304
305 CallAfter( [this, cfg]()
306 {
307 if( cfg.sidebar_collapsed )
309 else
310 m_splitterMainWindow->SetSashPosition( cfg.sash_pos );
311
313
314 m_splitter_left->SetSashPosition( cfg.variant_sash_pos );
315 } );
316
317 if( m_job )
318 m_outputFileName->SetValue( m_job->GetConfiguredOutputPath() );
319 else
320 m_outputFileName->SetValue( m_schSettings.m_BomExportFileName );
321
322 Center();
323
324 // Connect Events
325 m_grid->Bind( wxEVT_GRID_COL_SORT, &DIALOG_SYMBOL_FIELDS_TABLE::OnColSort, this );
326 m_grid->Bind( wxEVT_GRID_COL_MOVE, &DIALOG_SYMBOL_FIELDS_TABLE::OnColMove, this );
329 m_viewControlsGrid->Bind( wxEVT_GRID_CELL_CHANGED, &DIALOG_SYMBOL_FIELDS_TABLE::OnViewControlsCellChanged, this );
330
331 if( !m_job )
332 {
333 // Start listening for schematic changes
334 m_parent->Schematic().AddListener( this );
335 }
336 else
337 {
338 // Don't allow editing
339 m_grid->EnableEditing( false );
340 m_buttonApply->Hide();
341 m_buttonExport->Hide();
342 }
343}
344
345
347{
349 m_schSettings.m_BomExportFileName = m_outputFileName->GetValue();
350
351 EESCHEMA_SETTINGS::PANEL_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
352
353 if( !cfg.sidebar_collapsed )
354 cfg.sash_pos = m_splitterMainWindow->GetSashPosition();
355
356 cfg.variant_sash_pos = m_splitter_left->GetSashPosition();
357
358 for( int i = 0; i < m_grid->GetNumberCols(); i++ )
359 {
360 if( m_grid->IsColShown( i ) )
361 {
362 std::string fieldName( m_dataModel->GetColFieldName( i ).ToUTF8() );
363 cfg.field_widths[fieldName] = m_grid->GetColSize( i );
364 }
365 }
366
367 // Disconnect Events
368 m_grid->Unbind( wxEVT_GRID_COL_SORT, &DIALOG_SYMBOL_FIELDS_TABLE::OnColSort, this );
369 m_grid->Unbind( wxEVT_GRID_COL_SORT, &DIALOG_SYMBOL_FIELDS_TABLE::OnColMove, this );
372 m_viewControlsGrid->Unbind( wxEVT_GRID_CELL_CHANGED, &DIALOG_SYMBOL_FIELDS_TABLE::OnViewControlsCellChanged, this );
373
374 // Delete the GRID_TRICKS.
375 m_viewControlsGrid->PopEventHandler( true );
376 m_grid->PopEventHandler( true );
377
378 // we gave ownership of m_viewControlsDataModel & m_dataModel to the wxGrids...
379}
380
381
382void DIALOG_SYMBOL_FIELDS_TABLE::setSideBarButtonLook( bool aIsLeftPanelCollapsed )
383{
384 // Set bitmap and tooltip according to left panel visibility
385
386 if( aIsLeftPanelCollapsed )
387 {
389 m_sidebarButton->SetToolTip( _( "Expand left panel" ) );
390 }
391 else
392 {
394 m_sidebarButton->SetToolTip( _( "Collapse left panel" ) );
395 }
396}
397
398
400{
401 wxGridCellAttr* attr = new wxGridCellAttr;
402 attr->SetReadOnly( false );
403
404 // Set some column types to specific editors
405 if( m_dataModel->ColIsReference( aCol ) )
406 {
407 attr->SetReadOnly();
408 attr->SetRenderer( new GRID_CELL_TEXT_RENDERER() );
409 m_dataModel->SetColAttr( attr, aCol );
410 }
411 else if( m_dataModel->GetColFieldName( aCol ) == GetCanonicalFieldName( FIELD_T::FOOTPRINT ) )
412 {
413 attr->SetEditor( new GRID_CELL_FPID_EDITOR( this, wxEmptyString ) );
414 m_dataModel->SetColAttr( attr, aCol );
415 }
416 else if( m_dataModel->GetColFieldName( aCol ) == GetCanonicalFieldName( FIELD_T::DATASHEET ) )
417 {
418 // set datasheet column viewer button
419 attr->SetEditor( new GRID_CELL_URL_EDITOR( this, PROJECT_SCH::SchSearchS( &Prj() ),
420 { &m_parent->Schematic() } ) );
421 m_dataModel->SetColAttr( attr, aCol );
422 }
423 else if( m_dataModel->ColIsQuantity( aCol ) || m_dataModel->ColIsItemNumber( aCol ) )
424 {
425 attr->SetReadOnly();
426 attr->SetAlignment( wxALIGN_RIGHT, wxALIGN_CENTER );
427 attr->SetRenderer( new wxGridCellNumberRenderer() );
428 m_dataModel->SetColAttr( attr, aCol );
429 }
430 else if( m_dataModel->ColIsAttribute( aCol ) )
431 {
432 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
433 attr->SetRenderer( new GRID_CELL_CHECKBOX_RENDERER() );
434 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
435 m_dataModel->SetColAttr( attr, aCol );
436 }
437 else if( IsGeneratedField( m_dataModel->GetColFieldName( aCol ) ) )
438 {
439 attr->SetReadOnly();
440 m_dataModel->SetColAttr( attr, aCol );
441 }
442 else
443 {
444 attr->SetRenderer( new GRID_CELL_TEXT_RENDERER() );
445 attr->SetEditor( m_grid->GetDefaultEditor() );
446 m_dataModel->SetColAttr( attr, aCol );
447 }
448}
449
450
452{
453 EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
454 wxSize defaultDlgSize = ConvertDialogToPixels( wxSize( 600, 300 ) );
455
456 // Restore column sorting order and widths
457 m_grid->AutoSizeColumns( false );
458 int sortCol = 0;
459 bool sortAscending = true;
460
461 for( int col = 0; col < m_grid->GetNumberCols(); ++col )
462 {
464
465 if( col == m_dataModel->GetSortCol() )
466 {
467 sortCol = col;
468 sortAscending = m_dataModel->GetSortAsc();
469 }
470 }
471
472 // sync m_grid's column visibilities to Show checkboxes in m_viewControlsGrid
473 for( int i = 0; i < m_viewControlsDataModel->GetNumberRows(); ++i )
474 {
475 int col = m_dataModel->GetFieldNameCol( m_viewControlsDataModel->GetCanonicalFieldName( i ) );
476
477 if( col == -1 )
478 continue;
479
480 bool show = m_viewControlsDataModel->GetValueAsBool( i, SHOW_FIELD_COLUMN );
481 m_dataModel->SetShowColumn( col, show );
482
483 if( show )
484 {
485 m_grid->ShowCol( col );
486
487 std::string key( m_dataModel->GetColFieldName( col ).ToUTF8() );
488
489 if( cfg->m_FieldEditorPanel.field_widths.count( key )
490 && ( cfg->m_FieldEditorPanel.field_widths.at( key ) > 0 ) )
491 {
492 m_grid->SetColSize( col, cfg->m_FieldEditorPanel.field_widths.at( key ) );
493 }
494 else
495 {
496 int textWidth = m_dataModel->GetDataWidth( col ) + COLUMN_MARGIN;
497 int maxWidth = defaultDlgSize.x / 3;
498
499 m_grid->SetColSize( col, std::clamp( textWidth, 100, maxWidth ) );
500 }
501 }
502 else
503 {
504 m_grid->HideCol( col );
505 }
506 }
507
508 m_dataModel->SetSorting( sortCol, sortAscending );
509 m_grid->SetSortingColumn( sortCol, sortAscending );
510}
511
512
514{
515 if( !wxDialog::TransferDataToWindow() )
516 return false;
517
518 LoadFieldNames(); // loads rows into m_viewControlsDataModel and columns into m_dataModel
519
520 // Load our BOM view presets
521 SetUserBomPresets( m_schSettings.m_BomPresets );
522
523 BOM_PRESET preset = m_schSettings.m_BomSettings;
524
525 if( m_job )
526 {
527 preset.name = m_job->m_bomPresetName;
528 preset.excludeDNP = m_job->m_excludeDNP;
529 preset.filterString = m_job->m_filterString;
530 preset.sortAsc = m_job->m_sortAsc;
531 preset.sortField = m_job->m_sortField;
532 preset.groupSymbols = m_job->m_groupSymbols;
533
534 preset.fieldsOrdered.clear();
535
536 size_t i = 0;
537
538 for( const wxString& fieldName : m_job->m_fieldsOrdered )
539 {
540 BOM_FIELD field;
541 field.name = fieldName;
542 field.show = !fieldName.StartsWith( wxT( "__" ), &field.name );
543 field.groupBy = alg::contains( m_job->m_fieldsGroupBy, field.name );
544
545 if( ( m_job->m_fieldsLabels.size() > i ) && !m_job->m_fieldsLabels[i].IsEmpty() )
546 field.label = m_job->m_fieldsLabels[i];
547 else if( IsGeneratedField( field.name ) )
548 field.label = GetGeneratedFieldDisplayName( field.name );
549 else
550 field.label = field.name;
551
552 preset.fieldsOrdered.emplace_back( field );
553 i++;
554 }
555 }
556
557 ApplyBomPreset( preset );
559
560 // Load BOM export format presets
561 SetUserBomFmtPresets( m_schSettings.m_BomFmtPresets );
562 BOM_FMT_PRESET fmtPreset = m_schSettings.m_BomFmtSettings;
563
564 if( m_job )
565 {
566 fmtPreset.name = m_job->m_bomFmtPresetName;
567 fmtPreset.fieldDelimiter = m_job->m_fieldDelimiter;
568 fmtPreset.keepLineBreaks = m_job->m_keepLineBreaks;
569 fmtPreset.keepTabs = m_job->m_keepTabs;
570 fmtPreset.refDelimiter = m_job->m_refDelimiter;
571 fmtPreset.refRangeDelimiter = m_job->m_refRangeDelimiter;
572 fmtPreset.stringDelimiter = m_job->m_stringDelimiter;
573 }
574
575 ApplyBomFmtPreset( fmtPreset );
577
578 TOOL_MANAGER* toolMgr = m_parent->GetToolManager();
579 SCH_SELECTION_TOOL* selectionTool = toolMgr->GetTool<SCH_SELECTION_TOOL>();
580 SCH_SELECTION& selection = selectionTool->GetSelection();
581 SCH_SYMBOL* symbol = nullptr;
582
583 m_dataModel->SetGroupingEnabled( m_groupSymbolsBox->GetValue() );
584
585 wxCommandEvent dummy;
586 OnScope( dummy );
587
588 if( selection.GetSize() == 1 )
589 {
590 EDA_ITEM* item = selection.Front();
591
592 if( item->Type() == SCH_SYMBOL_T )
593 symbol = (SCH_SYMBOL*) item;
594 else if( item->GetParent() && item->GetParent()->Type() == SCH_SYMBOL_T )
595 symbol = (SCH_SYMBOL*) item->GetParent();
596 }
597
598 if( symbol )
599 {
600 for( int row = 0; row < m_dataModel->GetNumberRows(); ++row )
601 {
602 std::vector<SCH_REFERENCE> references = m_dataModel->GetRowReferences( row );
603 bool found = false;
604
605 for( const SCH_REFERENCE& ref : references )
606 {
607 if( ref.GetSymbol() == symbol )
608 {
609 found = true;
610 break;
611 }
612 }
613
614 if( found )
615 {
616 // Find the value column and the reference column if they're shown
617 int valueCol = -1;
618 int refCol = -1;
619 int anyCol = -1;
620
621 for( int col = 0; col < m_dataModel->GetNumberCols(); col++ )
622 {
623 if( m_dataModel->ColIsValue( col ) )
624 valueCol = col;
625 else if( m_dataModel->ColIsReference( col ) )
626 refCol = col;
627 else if( anyCol == -1 && m_dataModel->GetShowColumn( col ) )
628 anyCol = col;
629 }
630
631 if( valueCol != -1 && m_dataModel->GetShowColumn( valueCol ) )
632 m_grid->GoToCell( row, valueCol );
633 else if( refCol != -1 && m_dataModel->GetShowColumn( refCol ) )
634 m_grid->GoToCell( row, refCol );
635 else if( anyCol != -1 )
636 m_grid->GoToCell( row, anyCol );
637
638 break;
639 }
640 }
641 }
642
643 // We don't want table range selection events to happen until we've loaded the data or we
644 // we'll clear our selection as the grid is built before the code above can get the
645 // user's current selection.
647
648 return true;
649}
650
651
653{
654 if( !m_grid->CommitPendingChanges() )
655 return false;
656
657 if( !wxDialog::TransferDataFromWindow() )
658 return false;
659
660 if( m_job )
661 {
662 // and exit, don't even dream of saving changes from the data model
663 return true;
664 }
665
666 SCH_COMMIT commit( m_parent );
667 SCH_SHEET_PATH currentSheet = m_parent->GetCurrentSheet();
668 wxString currentVariant = m_parent->Schematic().GetCurrentVariant();
669
670 m_dataModel->ApplyData( commit, m_schSettings.m_TemplateFieldNames, currentVariant );
671
672 if( !commit.Empty() )
673 {
674 commit.Push( wxS( "Symbol Fields Table Edit" ) ); // Push clears the commit buffer.
675 m_parent->OnModify();
676 }
677
678 // Reset the view to where we left the user
679 m_parent->SetCurrentSheet( currentSheet );
680 m_parent->SyncView();
681 m_parent->Refresh();
682
683 return true;
684}
685
686
687void DIALOG_SYMBOL_FIELDS_TABLE::AddField( const wxString& aFieldName, const wxString& aLabelValue,
688 bool show, bool groupBy, bool addedByUser )
689{
690 // Users can add fields with variable names that match the special names in the grid,
691 // e.g. ${QUANTITY} so make sure we don't add them twice
692 for( int row = 0; row < m_viewControlsDataModel->GetNumberRows(); row++ )
693 {
694 if( m_viewControlsDataModel->GetCanonicalFieldName( row ) == aFieldName )
695 return;
696 }
697
698 m_dataModel->AddColumn( aFieldName, aLabelValue, addedByUser, m_parent->Schematic().GetCurrentVariant() );
699
700 wxGridTableMessage msg( m_dataModel, wxGRIDTABLE_NOTIFY_COLS_APPENDED, 1 );
701 m_grid->ProcessTableMessage( msg );
702
703 m_viewControlsGrid->OnAddRow(
704 [&]() -> std::pair<int, int>
705 {
706 m_viewControlsDataModel->AppendRow( aFieldName, aLabelValue, show, groupBy );
707
708 return { m_viewControlsDataModel->GetNumberRows() - 1, -1 };
709 } );
710}
711
712
714{
715 auto addMandatoryField =
716 [&]( FIELD_T fieldId, bool show, bool groupBy )
717 {
718 m_mandatoryFieldListIndexes[fieldId] = m_viewControlsDataModel->GetNumberRows();
719
721 show, groupBy );
722 };
723
724 // Add mandatory fields first show groupBy
725 addMandatoryField( FIELD_T::REFERENCE, true, true );
726 addMandatoryField( FIELD_T::VALUE, true, true );
727 addMandatoryField( FIELD_T::FOOTPRINT, true, true );
728 addMandatoryField( FIELD_T::DATASHEET, true, false );
729 addMandatoryField( FIELD_T::DESCRIPTION, false, false );
730
731 // Generated fields present only in the fields table
734
735 // User fields next
736 std::set<wxString> userFieldNames;
737
738 for( int ii = 0; ii < (int) m_symbolsList.GetCount(); ++ii )
739 {
740 SCH_SYMBOL* symbol = m_symbolsList[ii].GetSymbol();
741
742 for( const SCH_FIELD& field : symbol->GetFields() )
743 {
744 if( !field.IsMandatory() && !field.IsPrivate() )
745 userFieldNames.insert( field.GetName() );
746 }
747 }
748
749 for( const wxString& fieldName : userFieldNames )
750 AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), true, false );
751
752 // Add any templateFieldNames which aren't already present in the userFieldNames
753 for( const TEMPLATE_FIELDNAME& tfn : m_schSettings.m_TemplateFieldNames.GetTemplateFieldNames() )
754 {
755 if( userFieldNames.count( tfn.m_Name ) == 0 )
756 AddField( tfn.m_Name, GetGeneratedFieldDisplayName( tfn.m_Name ), false, false );
757 }
758}
759
760
761void DIALOG_SYMBOL_FIELDS_TABLE::OnAddField( wxCommandEvent& event )
762{
763 wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Add Field" ) );
764
765 if( dlg.ShowModal() != wxID_OK )
766 return;
767
768 wxString fieldName = dlg.GetValue();
769
770 if( fieldName.IsEmpty() )
771 {
772 DisplayError( this, _( "Field must have a name." ) );
773 return;
774 }
775
776 for( int i = 0; i < m_dataModel->GetNumberCols(); ++i )
777 {
778 if( fieldName == m_dataModel->GetColFieldName( i ) )
779 {
780 DisplayError( this, wxString::Format( _( "Field name '%s' already in use." ), fieldName ) );
781 return;
782 }
783 }
784
785 AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), true, false, true );
786
787 SetupColumnProperties( m_dataModel->GetColsCount() - 1 );
788
790 OnModify();
791}
792
793
794void DIALOG_SYMBOL_FIELDS_TABLE::OnRemoveField( wxCommandEvent& event )
795{
796 m_viewControlsGrid->OnDeleteRows(
797 [&]( int row )
798 {
799 for( FIELD_T id : MANDATORY_FIELDS )
800 {
801 if( m_mandatoryFieldListIndexes[id] == row )
802 {
803 DisplayError( this, wxString::Format( _( "The first %d fields are mandatory." ),
804 (int) m_mandatoryFieldListIndexes.size() ) );
805 return false;
806 }
807 }
808
809 return IsOK( this, wxString::Format( _( "Are you sure you want to remove the field '%s'?" ),
810 m_viewControlsDataModel->GetValue( row, DISPLAY_NAME_COLUMN ) ) );
811 },
812 [&]( int row )
813 {
814 wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
815 int col = m_dataModel->GetFieldNameCol( fieldName );
816
817 if( col != -1 )
818 m_dataModel->RemoveColumn( col );
819
820 m_viewControlsDataModel->DeleteRow( row );
821
823 OnModify();
824 } );
825}
826
827
828void DIALOG_SYMBOL_FIELDS_TABLE::OnRenameField( wxCommandEvent& event )
829{
830 wxArrayInt selectedRows = m_viewControlsGrid->GetSelectedRows();
831
832 if( selectedRows.empty() && m_viewControlsGrid->GetGridCursorRow() >= 0 )
833 selectedRows.push_back( m_viewControlsGrid->GetGridCursorRow() );
834
835 if( selectedRows.empty() )
836 return;
837
838 int row = selectedRows[0];
839
840 for( FIELD_T id : MANDATORY_FIELDS )
841 {
842 if( m_mandatoryFieldListIndexes[id] == row )
843 {
844 DisplayError( this, wxString::Format( _( "The first %d fields are mandatory and names cannot be changed." ),
845 (int) m_mandatoryFieldListIndexes.size() ) );
846 return;
847 }
848 }
849
850 wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
851 wxString label = m_viewControlsDataModel->GetValue( row, LABEL_COLUMN );
852 bool labelIsAutogenerated = label.IsSameAs( GetGeneratedFieldDisplayName( fieldName ) );
853
854 int col = m_dataModel->GetFieldNameCol( fieldName );
855 wxCHECK_RET( col != -1, wxS( "Existing field name missing from data model" ) );
856
857 wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Rename Field" ), fieldName );
858
859 if( dlg.ShowModal() != wxID_OK )
860 return;
861
862 wxString newFieldName = dlg.GetValue();
863
864 // No change, no-op
865 if( newFieldName == fieldName )
866 return;
867
868 // New field name already exists
869 if( m_dataModel->GetFieldNameCol( newFieldName ) != -1 )
870 {
871 wxString confirm_msg = wxString::Format( _( "Field name %s already exists." ), newFieldName );
872 DisplayError( this, confirm_msg );
873 return;
874 }
875
876 m_dataModel->RenameColumn( col, newFieldName );
877 m_viewControlsDataModel->SetCanonicalFieldName( row, newFieldName );
878 m_viewControlsDataModel->SetValue( row, DISPLAY_NAME_COLUMN, newFieldName );
879
880 if( labelIsAutogenerated )
881 {
882 m_viewControlsDataModel->SetValue( row, LABEL_COLUMN, GetGeneratedFieldDisplayName( newFieldName ) );
883 wxGridEvent evt( m_viewControlsGrid->GetId(), wxEVT_GRID_CELL_CHANGED, m_viewControlsGrid, row, LABEL_COLUMN );
885 }
886
888 OnModify();
889}
890
891
892void DIALOG_SYMBOL_FIELDS_TABLE::OnFilterText( wxCommandEvent& aEvent )
893{
894 m_dataModel->SetFilter( m_filter->GetValue() );
895 m_dataModel->RebuildRows();
896 m_grid->ForceRefresh();
897
899}
900
901
903{
904#if defined( __WXOSX__ ) // Doesn't work properly on other ports
905 wxPoint pos = aEvent.GetPosition();
906 wxRect ctrlRect = m_filter->GetScreenRect();
907 int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
908
909 // TODO: restore cursor when mouse leaves the filter field (or is it a MSW bug?)
910 if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
911 SetCursor( wxCURSOR_ARROW );
912 else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
913 SetCursor( wxCURSOR_ARROW );
914 else
915 SetCursor( wxCURSOR_IBEAM );
916#endif
917}
918
919
921{
922 m_dataModel->SetPath( m_parent->GetCurrentSheet() );
923 m_dataModel->SetScope( aScope );
924 m_dataModel->RebuildRows();
925}
926
927
928void DIALOG_SYMBOL_FIELDS_TABLE::OnScope( wxCommandEvent& aEvent )
929{
930 switch( aEvent.GetSelection() )
931 {
932 case 0: setScope( SCOPE::SCOPE_ALL ); break;
933 case 1: setScope( SCOPE::SCOPE_SHEET ); break;
934 case 2: setScope( SCOPE::SCOPE_SHEET_RECURSIVE ); break;
935 }
936}
937
938
940{
941 m_dataModel->SetGroupingEnabled( m_groupSymbolsBox->GetValue() );
942 m_dataModel->RebuildRows();
943 m_grid->ForceRefresh();
944
946}
947
948
949void DIALOG_SYMBOL_FIELDS_TABLE::OnMenu( wxCommandEvent& event )
950{
951 EESCHEMA_SETTINGS::PANEL_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
952
953 // Build a pop menu:
954 wxMenu menu;
955
956 menu.Append( 4204, _( "Include 'DNP' Symbols" ),
957 _( "Show symbols marked 'DNP' in the table. This setting also controls whether or not 'DNP' "
958 "symbols are included on export." ),
959 wxITEM_CHECK );
960 menu.Check( 4204, !m_dataModel->GetExcludeDNP() );
961
962 menu.Append( 4205, _( "Include 'Exclude from BOM' Symbols" ),
963 _( "Show symbols marked 'Exclude from BOM' in the table. Symbols marked 'Exclude from BOM' "
964 "are never included on export." ),
965 wxITEM_CHECK );
966 menu.Check( 4205, m_dataModel->GetIncludeExcludedFromBOM() );
967
968 menu.AppendSeparator();
969
970 menu.Append( 4206, _( "Highlight on Cross-probe" ),
971 _( "Highlight corresponding item on canvas when it is selected in the table" ),
972 wxITEM_CHECK );
973 menu.Check( 4206, cfg.selection_mode == 0 );
974
975 menu.Append( 4207, _( "Select on Cross-probe" ),
976 _( "Select corresponding item on canvas when it is selected in the table" ),
977 wxITEM_CHECK );
978 menu.Check( 4207, cfg.selection_mode == 1 );
979
980 // menu_id is the selected submenu id from the popup menu or wxID_NONE
981 int menu_id = m_bMenu->GetPopupMenuSelectionFromUser( menu );
982
983 if( menu_id == 0 || menu_id == 4204 )
984 {
985 m_dataModel->SetExcludeDNP( !m_dataModel->GetExcludeDNP() );
986 m_dataModel->RebuildRows();
987 m_grid->ForceRefresh();
988
990 }
991 else if( menu_id == 1 || menu_id == 4205 )
992 {
993 m_dataModel->SetIncludeExcludedFromBOM( !m_dataModel->GetIncludeExcludedFromBOM() );
994 m_dataModel->RebuildRows();
995 m_grid->ForceRefresh();
996
998 }
999 else if( menu_id == 3 || menu_id == 4206 )
1000 {
1001 if( cfg.selection_mode != 0 )
1002 cfg.selection_mode = 0;
1003 else
1004 cfg.selection_mode = 2;
1005 }
1006 else if( menu_id == 4 || menu_id == 4207 )
1007 {
1008 if( cfg.selection_mode != 1 )
1009 cfg.selection_mode = 1;
1010 else
1011 cfg.selection_mode = 2;
1012 }
1013}
1014
1015
1016void DIALOG_SYMBOL_FIELDS_TABLE::OnColSort( wxGridEvent& aEvent )
1017{
1018 int sortCol = aEvent.GetCol();
1019 std::string key( m_dataModel->GetColFieldName( sortCol ).ToUTF8() );
1020 bool ascending;
1021
1022 // Don't sort by item number, it is generated by the sort
1023 if( m_dataModel->ColIsItemNumber( sortCol ) )
1024 {
1025 aEvent.Veto();
1026 return;
1027 }
1028
1029 // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the event, and
1030 // if we ask it will give us pre-event info.
1031 if( m_grid->IsSortingBy( sortCol ) )
1032 {
1033 // same column; invert ascending
1034 ascending = !m_grid->IsSortOrderAscending();
1035 }
1036 else
1037 {
1038 // different column; start with ascending
1039 ascending = true;
1040 }
1041
1042 m_dataModel->SetSorting( sortCol, ascending );
1043 m_dataModel->RebuildRows();
1044 m_grid->ForceRefresh();
1045
1047}
1048
1049
1050void DIALOG_SYMBOL_FIELDS_TABLE::OnColMove( wxGridEvent& aEvent )
1051{
1052 int origPos = aEvent.GetCol();
1053
1054 // Save column widths since the setup function uses the saved config values
1055 EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
1056
1057 for( int i = 0; i < m_grid->GetNumberCols(); i++ )
1058 {
1059 if( m_grid->IsColShown( i ) )
1060 {
1061 std::string fieldName( m_dataModel->GetColFieldName( i ).ToUTF8() );
1062 cfg->m_FieldEditorPanel.field_widths[fieldName] = m_grid->GetColSize( i );
1063 }
1064 }
1065
1066 CallAfter(
1067 [origPos, this]()
1068 {
1069 int newPos = m_grid->GetColPos( origPos );
1070
1071#ifdef __WXMAC__
1072 if( newPos < origPos )
1073 newPos += 1;
1074#endif
1075
1076 m_dataModel->MoveColumn( origPos, newPos );
1077
1078 // "Unmove" the column since we've moved the column internally
1079 m_grid->ResetColPos();
1080
1081 // We need to reset all the column attr's to the correct column order
1083
1084 m_grid->ForceRefresh();
1085 } );
1086
1088}
1089
1090
1092{
1093 if( aShow )
1094 m_grid->ShowCol( aCol );
1095 else
1096 m_grid->HideCol( aCol );
1097
1098 m_dataModel->SetShowColumn( aCol, aShow );
1099
1101
1102 if( m_nbPages->GetSelection() == 1 )
1104 else
1105 m_grid->ForceRefresh();
1106
1107 OnModify();
1108}
1109
1110
1112{
1113 int row = aEvent.GetRow();
1114
1115 wxCHECK( row < m_viewControlsGrid->GetNumberRows(), /* void */ );
1116
1117 switch( aEvent.GetCol() )
1118 {
1119 case LABEL_COLUMN:
1120 {
1121 wxString label = m_viewControlsDataModel->GetValue( row, LABEL_COLUMN );
1122 wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
1123 int dataCol = m_dataModel->GetFieldNameCol( fieldName );
1124
1125 if( dataCol != -1 )
1126 {
1127 m_dataModel->SetColLabelValue( dataCol, label );
1128 m_grid->SetColLabelValue( dataCol, label );
1129
1130 if( m_nbPages->GetSelection() == 1 )
1132 else
1133 m_grid->ForceRefresh();
1134
1136 OnModify();
1137 }
1138
1139 break;
1140 }
1141
1142 case SHOW_FIELD_COLUMN:
1143 {
1144 wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
1145 bool value = m_viewControlsDataModel->GetValueAsBool( row, SHOW_FIELD_COLUMN );
1146 int dataCol = m_dataModel->GetFieldNameCol( fieldName );
1147
1148 if( dataCol != -1 )
1149 ShowHideColumn( dataCol, value );
1150
1151 break;
1152 }
1153
1154 case GROUP_BY_COLUMN:
1155 {
1156 wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
1157 bool value = m_viewControlsDataModel->GetValueAsBool( row, GROUP_BY_COLUMN );
1158 int dataCol = m_dataModel->GetFieldNameCol( fieldName );
1159
1160 if( m_dataModel->ColIsQuantity( dataCol ) && value )
1161 {
1162 DisplayError( this, _( "The Quantity column cannot be grouped by." ) );
1163
1164 value = false;
1165 m_viewControlsDataModel->SetValueAsBool( row, GROUP_BY_COLUMN, value );
1166 break;
1167 }
1168
1169 if( m_dataModel->ColIsItemNumber( dataCol ) && value )
1170 {
1171 DisplayError( this, _( "The Item Number column cannot be grouped by." ) );
1172
1173 value = false;
1174 m_viewControlsDataModel->SetValueAsBool( row, GROUP_BY_COLUMN, value );
1175 break;
1176 }
1177
1178 m_dataModel->SetGroupColumn( dataCol, value );
1179 m_dataModel->RebuildRows();
1180
1181 if( m_nbPages->GetSelection() == 1 )
1183 else
1184 m_grid->ForceRefresh();
1185
1187 OnModify();
1188 break;
1189 }
1190
1191 default:
1192 break;
1193 }
1194}
1195
1196
1198{
1199 m_grid->ForceRefresh();
1200}
1201
1202
1203void DIALOG_SYMBOL_FIELDS_TABLE::OnTableColSize( wxGridSizeEvent& aEvent )
1204{
1205 aEvent.Skip();
1206
1207 m_grid->ForceRefresh();
1208}
1209
1210
1212{
1213 m_dataModel->RebuildRows();
1214 m_grid->ForceRefresh();
1215}
1216
1217
1219{
1220 if( m_dataModel->IsExpanderColumn( event.GetCol() ) )
1221 {
1222 m_grid->ClearSelection();
1223
1224 m_dataModel->ExpandCollapseRow( event.GetRow() );
1225 m_grid->SetGridCursor( event.GetRow(), event.GetCol() );
1226 }
1227 else
1228 {
1229 event.Skip();
1230 }
1231}
1232
1233
1234void DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected( wxGridRangeSelectEvent& aEvent )
1235{
1236 EESCHEMA_SETTINGS::PANEL_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
1237
1238 // Cross-probing should only work in Edit page
1239 if( m_nbPages->GetSelection() != 0 )
1240 return;
1241
1242 // Multi-select can grab the rows that are expanded child refs, and also the row
1243 // containing the list of all child refs. Make sure we add refs/symbols uniquely
1244 std::set<SCH_REFERENCE> refs;
1245 std::set<SCH_ITEM*> symbols;
1246
1247 // This handler handles selecting and deselecting
1248 if( aEvent.Selecting() )
1249 {
1250 for( int i = aEvent.GetTopRow(); i <= aEvent.GetBottomRow(); i++ )
1251 {
1252 for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( i ) )
1253 refs.insert( ref );
1254 }
1255
1256 for( const SCH_REFERENCE& ref : refs )
1257 symbols.insert( ref.GetSymbol() );
1258 }
1259
1260 if( cfg.selection_mode == 0 )
1261 {
1262 SCH_EDITOR_CONTROL* editor = m_parent->GetToolManager()->GetTool<SCH_EDITOR_CONTROL>();
1263
1264 if( refs.size() > 0 )
1265 {
1266 // Use of full path based on UUID allows select of not yet annotated or duplicated
1267 // symbols
1268 wxString symbol_path = refs.begin()->GetFullPath();
1269
1270 // Focus only handles one item at this time
1271 editor->FindSymbolAndItem( &symbol_path, nullptr, true, HIGHLIGHT_SYMBOL, wxEmptyString );
1272 }
1273 else
1274 {
1275 m_parent->ClearFocus();
1276 }
1277 }
1278 else if( cfg.selection_mode == 1 )
1279 {
1280 SCH_SELECTION_TOOL* selTool = m_parent->GetToolManager()->GetTool<SCH_SELECTION_TOOL>();
1281 std::vector<SCH_ITEM*> items( symbols.begin(), symbols.end() );
1282
1283 if( refs.size() > 0 )
1284 selTool->SyncSelection( refs.begin()->GetSheetPath(), nullptr, items );
1285 else
1286 selTool->ClearSelection();
1287 }
1288}
1289
1290
1292{
1293 const wxString& showColLabel = m_viewControlsGrid->GetColLabelValue( SHOW_FIELD_COLUMN );
1294 const wxString& groupByColLabel = m_viewControlsGrid->GetColLabelValue( GROUP_BY_COLUMN );
1295 int showColWidth = KIUI::GetTextSize( showColLabel, m_viewControlsGrid ).x + COLUMN_MARGIN;
1296 int groupByColWidth = KIUI::GetTextSize( groupByColLabel, m_viewControlsGrid ).x + COLUMN_MARGIN;
1297 int remainingWidth = m_viewControlsGrid->GetSize().GetX() - showColWidth - groupByColWidth;
1298
1299 m_viewControlsGrid->SetColSize( showColWidth, SHOW_FIELD_COLUMN );
1300 m_viewControlsGrid->SetColSize( groupByColWidth, GROUP_BY_COLUMN );
1301
1302 if( m_viewControlsGrid->IsColShown( DISPLAY_NAME_COLUMN ) && m_viewControlsGrid->IsColShown( LABEL_COLUMN ) )
1303 {
1304 m_viewControlsGrid->SetColSize( DISPLAY_NAME_COLUMN, std::max( remainingWidth / 2, 60 ) );
1305 m_viewControlsGrid->SetColSize( LABEL_COLUMN, std::max( remainingWidth - ( remainingWidth / 2 ), 60 ) );
1306 }
1307 else if( m_viewControlsGrid->IsColShown( DISPLAY_NAME_COLUMN ) )
1308 {
1309 m_viewControlsGrid->SetColSize( DISPLAY_NAME_COLUMN, std::max( remainingWidth, 60 ) );
1310 }
1311 else if( m_viewControlsGrid->IsColShown( LABEL_COLUMN ) )
1312 {
1313 m_viewControlsGrid->SetColSize( LABEL_COLUMN, std::max( remainingWidth, 60 ) );
1314 }
1315
1316 event.Skip();
1317}
1318
1319
1321{
1323 {
1324 m_parent->SaveProject();
1325 ClearModify();
1326 }
1327}
1328
1329
1330void DIALOG_SYMBOL_FIELDS_TABLE::OnPageChanged( wxNotebookEvent& event )
1331{
1332 if( m_dataModel->GetColsCount() )
1334}
1335
1336
1338{
1341}
1342
1343
1345{
1346 bool saveIncludeExcudedFromBOM = m_dataModel->GetIncludeExcludedFromBOM();
1347
1348 m_dataModel->SetIncludeExcludedFromBOM( false );
1349 m_dataModel->RebuildRows();
1350
1351 m_textOutput->SetValue( m_dataModel->Export( GetCurrentBomFmtSettings() ) );
1352
1353 if( saveIncludeExcudedFromBOM )
1354 {
1355 m_dataModel->SetIncludeExcludedFromBOM( true );
1356 m_dataModel->RebuildRows();
1357 }
1358}
1359
1360
1362{
1363 BOM_FMT_PRESET current;
1364
1365 current.name = m_cbBomFmtPresets->GetStringSelection();
1366 current.fieldDelimiter = m_textFieldDelimiter->GetValue();
1367 current.stringDelimiter = m_textStringDelimiter->GetValue();
1368 current.refDelimiter = m_textRefDelimiter->GetValue();
1369 current.refRangeDelimiter = m_textRefRangeDelimiter->GetValue();
1370 current.keepTabs = m_checkKeepTabs->GetValue();
1371 current.keepLineBreaks = m_checkKeepLineBreaks->GetValue();
1372
1373 return current;
1374}
1375
1376
1378{
1379 m_nbPages->SetSelection( 0 );
1380}
1381
1382
1384{
1385 m_nbPages->SetSelection( 1 );
1386}
1387
1388
1390{
1391 // Build the absolute path of current output directory to preselect it in the file browser.
1392 wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() );
1393 path = Prj().AbsolutePath( path );
1394
1395
1396 // Calculate the export filename
1397 wxFileName fn( Prj().AbsolutePath( m_parent->Schematic().GetFileName() ) );
1398 fn.SetExt( FILEEXT::CsvFileExtension );
1399
1400 wxFileDialog saveDlg( this, _( "Bill of Materials Output File" ), path, fn.GetFullName(),
1401 FILEEXT::CsvFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
1402
1404
1405 if( saveDlg.ShowModal() == wxID_CANCEL )
1406 return;
1407
1408
1409 wxFileName file = wxFileName( saveDlg.GetPath() );
1410 wxString defaultPath = fn.GetPathWithSep();
1411
1412 if( IsOK( this, wxString::Format( _( "Do you want to use a path relative to\n'%s'?" ), defaultPath ) ) )
1413 {
1414 if( !file.MakeRelativeTo( defaultPath ) )
1415 {
1416 DisplayErrorMessage( this, _( "Cannot make path relative (target volume different from schematic "
1417 "file volume)!" ) );
1418 }
1419 }
1420
1421 m_outputFileName->SetValue( file.GetFullPath() );
1422}
1423
1424
1426{
1427 EESCHEMA_SETTINGS::PANEL_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
1428
1429 if( cfg.sidebar_collapsed )
1430 {
1431 cfg.sidebar_collapsed = false;
1432 m_splitterMainWindow->SplitVertically( m_leftPanel, m_rightPanel, cfg.sash_pos );
1433 }
1434 else
1435 {
1436 cfg.sash_pos = m_splitterMainWindow->GetSashPosition();
1437
1438 cfg.sidebar_collapsed = true;
1440 }
1441
1443}
1444
1445
1446void DIALOG_SYMBOL_FIELDS_TABLE::OnExport( wxCommandEvent& aEvent )
1447{
1448 if( m_dataModel->IsEdited() )
1449 {
1450 if( OKOrCancelDialog( nullptr, _( "Unsaved data" ),
1451 _( "Changes have not yet been saved. Export unsaved data?" ), "",
1452 _( "OK" ), _( "Cancel" ) )
1453 == wxID_CANCEL )
1454 {
1455 return;
1456 }
1457 }
1458
1459 // Create output directory if it does not exist (also transform it in absolute form).
1460 // Bail if it fails.
1461
1462 std::function<bool( wxString* )> textResolver =
1463 [&]( wxString* token ) -> bool
1464 {
1465 SCHEMATIC& schematic = m_parent->Schematic();
1466
1467 // Handles m_board->GetTitleBlock() *and* m_board->GetProject()
1468 return schematic.ResolveTextVar( &schematic.CurrentSheet(), token, 0 );
1469 };
1470
1471 wxString path = m_outputFileName->GetValue();
1472
1473 if( path.IsEmpty() )
1474 {
1475 DisplayError( this, _( "No output file specified in Export tab." ) );
1476 return;
1477 }
1478
1479 path = ExpandTextVars( path, &textResolver );
1481
1482 wxFileName outputFile = wxFileName::FileName( path );
1483 wxString msg;
1484
1485 if( !EnsureFileDirectoryExists( &outputFile, Prj().AbsolutePath( m_parent->Schematic().GetFileName() ),
1487 {
1488 msg.Printf( _( "Could not open/create path '%s'." ), outputFile.GetPath() );
1489 DisplayError( this, msg );
1490 return;
1491 }
1492
1493 wxFFile out( outputFile.GetFullPath(), "wb" );
1494
1495 if( !out.IsOpened() )
1496 {
1497 msg.Printf( _( "Could not create BOM output '%s'." ), outputFile.GetFullPath() );
1498 DisplayError( this, msg );
1499 return;
1500 }
1501
1503
1504 if( !out.Write( m_textOutput->GetValue() ) )
1505 {
1506 msg.Printf( _( "Could not write BOM output '%s'." ), outputFile.GetFullPath() );
1507 DisplayError( this, msg );
1508 return;
1509 }
1510
1511 // close the file before we tell the user it's done with the info modal :workflow meme:
1512 out.Close();
1513 msg.Printf( _( "Wrote BOM output to '%s'" ), outputFile.GetFullPath() );
1514 DisplayInfoMessage( this, msg );
1515}
1516
1517
1518void DIALOG_SYMBOL_FIELDS_TABLE::OnCancel( wxCommandEvent& aEvent )
1519{
1520 if( m_job )
1521 EndModal( wxID_CANCEL );
1522 else
1523 Close();
1524}
1525
1526
1527void DIALOG_SYMBOL_FIELDS_TABLE::OnOk( wxCommandEvent& aEvent )
1528{
1530
1531 if( m_job )
1532 {
1533 m_job->SetConfiguredOutputPath( m_outputFileName->GetValue() );
1534
1536 m_job->m_bomFmtPresetName = m_currentBomFmtPreset->name;
1537 else
1538 m_job->m_bomFmtPresetName = wxEmptyString;
1539
1540 if( m_currentBomPreset )
1541 m_job->m_bomPresetName = m_currentBomPreset->name;
1542 else
1543 m_job->m_bomPresetName = wxEmptyString;
1544
1546 m_job->m_fieldDelimiter = fmtSettings.fieldDelimiter;
1547 m_job->m_stringDelimiter = fmtSettings.stringDelimiter;
1548 m_job->m_refDelimiter = fmtSettings.refDelimiter;
1549 m_job->m_refRangeDelimiter = fmtSettings.refRangeDelimiter;
1550 m_job->m_keepTabs = fmtSettings.keepTabs;
1551 m_job->m_keepLineBreaks = fmtSettings.keepLineBreaks;
1552
1553 BOM_PRESET presetFields = m_dataModel->GetBomSettings();
1554 m_job->m_sortAsc = presetFields.sortAsc;
1555 m_job->m_excludeDNP = presetFields.excludeDNP;
1556 m_job->m_filterString = presetFields.filterString;
1557 m_job->m_sortField = presetFields.sortField;
1558 m_job->m_groupSymbols = presetFields.groupSymbols;
1559
1560 m_job->m_fieldsOrdered.clear();
1561 m_job->m_fieldsLabels.clear();
1562 m_job->m_fieldsGroupBy.clear();
1563
1564 for( const BOM_FIELD& modelField : m_dataModel->GetFieldsOrdered() )
1565 {
1566 if( modelField.show )
1567 m_job->m_fieldsOrdered.emplace_back( modelField.name );
1568 else
1569 m_job->m_fieldsOrdered.emplace_back( wxT( "__" ) + modelField.name );
1570
1571 m_job->m_fieldsLabels.emplace_back( modelField.label );
1572
1573 if( modelField.groupBy )
1574 m_job->m_fieldsGroupBy.emplace_back( modelField.name );
1575 }
1576
1577 wxString selectedVariant = getSelectedVariant();
1578
1579 if( !selectedVariant.IsEmpty() )
1580 m_job->m_variantNames.push_back( selectedVariant );
1581
1582 EndModal( wxID_OK );
1583 }
1584 else
1585 {
1586 Close();
1587 }
1588}
1589
1590
1591void DIALOG_SYMBOL_FIELDS_TABLE::OnClose( wxCloseEvent& aEvent )
1592{
1593 if( m_job )
1594 {
1595 aEvent.Skip();
1596 return;
1597 }
1598
1599 m_grid->CommitPendingChanges( true );
1600
1601 if( m_dataModel->IsEdited() && aEvent.CanVeto() )
1602 {
1603 if( !HandleUnsavedChanges( this, _( "Save changes?" ),
1604 [&]() -> bool
1605 {
1606 return TransferDataFromWindow();
1607 } ) )
1608 {
1609 aEvent.Veto();
1610 return;
1611 }
1612 }
1613
1614 // Stop listening to schematic events
1615 m_parent->Schematic().RemoveListener( this );
1616 m_parent->ClearFocus();
1617
1618 wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_CLOSE_DIALOG_SYMBOL_FIELDS_TABLE, wxID_ANY );
1619
1620 if( wxWindow* parent = GetParent() )
1621 wxQueueEvent( parent, evt );
1622}
1623
1624
1626{
1627 std::vector<BOM_PRESET> ret;
1628
1629 for( const std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
1630 {
1631 if( !pair.second.readOnly )
1632 ret.emplace_back( pair.second );
1633 }
1634
1635 return ret;
1636}
1637
1638
1639void DIALOG_SYMBOL_FIELDS_TABLE::SetUserBomPresets( std::vector<BOM_PRESET>& aPresetList )
1640{
1641 // Reset to defaults
1643
1644 for( const BOM_PRESET& preset : aPresetList )
1645 {
1646 if( m_bomPresets.count( preset.name ) )
1647 continue;
1648
1649 m_bomPresets[preset.name] = preset;
1650
1651 m_bomPresetMRU.Add( preset.name );
1652 }
1653
1655}
1656
1657
1658void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomPreset( const wxString& aPresetName )
1659{
1660 updateBomPresetSelection( aPresetName );
1661
1662 wxCommandEvent dummy;
1664}
1665
1666
1668{
1669 if( m_bomPresets.count( aPreset.name ) )
1671 else
1672 m_currentBomPreset = nullptr;
1673
1674 if( m_currentBomPreset && !m_currentBomPreset->readOnly )
1676 else
1677 m_lastSelectedBomPreset = nullptr;
1678
1679 updateBomPresetSelection( aPreset.name );
1680 doApplyBomPreset( aPreset );
1681}
1682
1683
1685{
1686 m_bomPresets.clear();
1687 m_bomPresetMRU.clear();
1688
1689 // Load the read-only defaults
1690 for( const BOM_PRESET& preset : BOM_PRESET::BuiltInPresets() )
1691 {
1692 m_bomPresets[preset.name] = preset;
1693 m_bomPresets[preset.name].readOnly = true;
1694
1695 m_bomPresetMRU.Add( preset.name );
1696 }
1697}
1698
1699
1701{
1702 m_cbBomPresets->Clear();
1703
1704 int idx = 0;
1705 int default_idx = 0;
1706
1707 for( const auto& [presetName, preset] : m_bomPresets )
1708 {
1709 m_cbBomPresets->Append( wxGetTranslation( presetName ), (void*) &preset );
1710
1711 if( presetName == BOM_PRESET::DefaultEditing().name )
1712 default_idx = idx;
1713
1714 idx++;
1715 }
1716
1717 m_cbBomPresets->Append( wxT( "---" ) );
1718 m_cbBomPresets->Append( _( "Save preset..." ) );
1719 m_cbBomPresets->Append( _( "Delete preset..." ) );
1720
1721 // At least the built-in presets should always be present
1722 wxASSERT( !m_bomPresets.empty() );
1723
1724 m_cbBomPresets->SetSelection( default_idx );
1725 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( default_idx ) );
1726}
1727
1728
1730{
1731 BOM_PRESET current = m_dataModel->GetBomSettings();
1732
1733 auto it = std::find_if( m_bomPresets.begin(), m_bomPresets.end(),
1734 [&]( const std::pair<const wxString, BOM_PRESET>& aPair )
1735 {
1736 const BOM_PRESET& preset = aPair.second;
1737
1738 // Check the simple settings first
1739 if( !( preset.sortAsc == current.sortAsc
1740 && preset.filterString == current.filterString
1741 && preset.groupSymbols == current.groupSymbols
1742 && preset.excludeDNP == current.excludeDNP
1743 && preset.includeExcludedFromBOM == current.includeExcludedFromBOM ) )
1744 {
1745 return false;
1746 }
1747
1748 // We should compare preset.name and current.name. Unfortunately current.name is
1749 // empty because m_dataModel->GetBomSettings() does not store the .name member.
1750 // So use sortField member as a (not very efficient) auxiliary filter.
1751 // As a further complication, sortField can be translated in m_bomPresets list, so
1752 // current.sortField needs to be translated.
1753 // Probably this not efficient and error prone test should be removed (JPC).
1754 if( preset.sortField != wxGetTranslation( current.sortField ) )
1755 return false;
1756
1757 // Only compare shown or grouped fields
1758 std::vector<BOM_FIELD> A, B;
1759
1760 for( const BOM_FIELD& field : preset.fieldsOrdered )
1761 {
1762 if( field.show || field.groupBy )
1763 A.emplace_back( field );
1764 }
1765
1766 for( const BOM_FIELD& field : current.fieldsOrdered )
1767 {
1768 if( field.show || field.groupBy )
1769 B.emplace_back( field );
1770 }
1771
1772 return A == B;
1773 } );
1774
1775 if( it != m_bomPresets.end() )
1776 {
1777 // Select the right m_cbBomPresets item.
1778 // but these items are translated if they are predefined items.
1779 bool do_translate = it->second.readOnly;
1780 wxString text = do_translate ? wxGetTranslation( it->first ) : it->first;
1781 m_cbBomPresets->SetStringSelection( text );
1782 }
1783 else
1784 {
1785 m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 ); // separator
1786 }
1787
1788 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( m_cbBomPresets->GetSelection() ) );
1789}
1790
1791
1793{
1794 // Look at m_userBomPresets to know if aName is a read only preset, or a user preset.
1795 // Read-only presets have translated names in UI, so we have to use a translated name
1796 // in UI selection. But for a user preset name we search for the untranslated aName.
1797 wxString ui_label = aName;
1798
1799 for( const auto& [presetName, preset] : m_bomPresets )
1800 {
1801 if( presetName == aName )
1802 {
1803 if( preset.readOnly == true )
1804 ui_label = wxGetTranslation( aName );
1805
1806 break;
1807 }
1808 }
1809
1810 int idx = m_cbBomPresets->FindString( ui_label );
1811
1812 if( idx >= 0 && m_cbBomPresets->GetSelection() != idx )
1813 {
1814 m_cbBomPresets->SetSelection( idx );
1815 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( idx ) );
1816 }
1817 else if( idx < 0 )
1818 {
1819 m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 ); // separator
1820 }
1821}
1822
1823
1825{
1826 int count = m_cbBomPresets->GetCount();
1827 int index = m_cbBomPresets->GetSelection();
1828
1829 auto resetSelection =
1830 [&]()
1831 {
1832 if( m_currentBomPreset )
1833 m_cbBomPresets->SetStringSelection( m_currentBomPreset->name );
1834 else
1835 m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 );
1836 };
1837
1838 if( index == count - 3 )
1839 {
1840 // Separator: reject the selection
1841 resetSelection();
1842 return;
1843 }
1844 else if( index == count - 2 )
1845 {
1846 // Save current state to new preset
1847 wxString name;
1848
1851
1852 wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
1853
1854 if( dlg.ShowModal() != wxID_OK )
1855 {
1856 resetSelection();
1857 return;
1858 }
1859
1860 name = dlg.GetValue();
1861 bool exists = m_bomPresets.count( name );
1862
1863 if( !exists )
1864 {
1865 m_bomPresets[name] = m_dataModel->GetBomSettings();
1866 m_bomPresets[name].readOnly = false;
1867 m_bomPresets[name].name = name;
1868 }
1869
1870 BOM_PRESET* preset = &m_bomPresets[name];
1871
1872 if( !exists )
1873 {
1874 index = m_cbBomPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
1875 }
1876 else if( preset->readOnly )
1877 {
1878 wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
1879 _( "Error" ), wxOK | wxICON_ERROR, this );
1880 resetSelection();
1881 return;
1882 }
1883 else
1884 {
1885 // Ask the user if they want to overwrite the existing preset
1886 if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
1887 {
1888 resetSelection();
1889 return;
1890 }
1891
1892 *preset = m_dataModel->GetBomSettings();
1893 preset->name = name;
1894
1895 index = m_cbBomPresets->FindString( name );
1896
1897 if( m_bomPresetMRU.Index( name ) != wxNOT_FOUND )
1898 m_bomPresetMRU.Remove( name );
1899 }
1900
1901 m_currentBomPreset = preset;
1902 m_cbBomPresets->SetSelection( index );
1903 m_bomPresetMRU.Insert( name, 0 );
1904
1905 return;
1906 }
1907 else if( index == count - 1 )
1908 {
1909 // Delete a preset
1910 wxArrayString headers;
1911 std::vector<wxArrayString> items;
1912
1913 headers.Add( _( "Presets" ) );
1914
1915 for( const auto& [name, preset] : m_bomPresets )
1916 {
1917 if( !preset.readOnly )
1918 {
1919 wxArrayString item;
1920 item.Add( name );
1921 items.emplace_back( item );
1922 }
1923 }
1924
1925 EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
1926 dlg.SetListLabel( _( "Select preset:" ) );
1927
1928 if( dlg.ShowModal() == wxID_OK )
1929 {
1930 wxString presetName = dlg.GetTextSelection();
1931 int idx = m_cbBomPresets->FindString( presetName );
1932
1933 if( idx != wxNOT_FOUND )
1934 {
1935 m_bomPresets.erase( presetName );
1936
1937 m_cbBomPresets->Delete( idx );
1938 m_currentBomPreset = nullptr;
1939 }
1940
1941 if( m_bomPresetMRU.Index( presetName ) != wxNOT_FOUND )
1942 m_bomPresetMRU.Remove( presetName );
1943 }
1944
1945 resetSelection();
1946 return;
1947 }
1948
1949 BOM_PRESET* preset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( index ) );
1950 m_currentBomPreset = preset;
1951
1952 m_lastSelectedBomPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
1953
1954 if( preset )
1955 {
1956 doApplyBomPreset( *preset );
1958 m_currentBomPreset = preset;
1959
1960 if( !m_currentBomPreset->name.IsEmpty() )
1961 {
1962 if( m_bomPresetMRU.Index( preset->name ) != wxNOT_FOUND )
1963 m_bomPresetMRU.Remove( preset->name );
1964
1965 m_bomPresetMRU.Insert( preset->name, 0 );
1966 }
1967 }
1968}
1969
1970
1972{
1973 // Disable rebuilds while we're applying the preset otherwise we'll be
1974 // rebuilding the model constantly while firing off wx events
1975 m_dataModel->DisableRebuilds();
1976
1977 // Basically, we apply the BOM preset to the data model and then
1978 // update our UI to reflect resulting the data model state, not the preset.
1979 m_dataModel->ApplyBomPreset( aPreset, m_parent->Schematic().GetCurrentVariant() );
1980
1981 // BOM Presets can add, but not remove, columns, so make sure the view controls
1982 // grid has all of them before starting
1983 for( int i = 0; i < m_dataModel->GetColsCount(); i++ )
1984 {
1985 const wxString& fieldName( m_dataModel->GetColFieldName( i ) );
1986 bool found = false;
1987
1988 for( int j = 0; j < m_viewControlsDataModel->GetNumberRows(); j++ )
1989 {
1990 if( m_viewControlsDataModel->GetCanonicalFieldName( j ) == fieldName )
1991 {
1992 found = true;
1993 break;
1994 }
1995 }
1996
1997 // Properties like label, etc. will be added in the next loop
1998 if( !found )
1999 AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), false, false );
2000 }
2001
2002 // Sync all fields
2003 for( int i = 0; i < m_viewControlsDataModel->GetNumberRows(); i++ )
2004 {
2005 const wxString& fieldName( m_viewControlsDataModel->GetCanonicalFieldName( i ) );
2006 int col = m_dataModel->GetFieldNameCol( fieldName );
2007
2008 if( col == -1 )
2009 {
2010 wxASSERT_MSG( true, "Fields control has a field not found in the data model." );
2011 continue;
2012 }
2013
2014 EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
2015 std::string fieldNameStr( fieldName.ToUTF8() );
2016
2017 // Set column labels
2018 const wxString& label = m_dataModel->GetColLabelValue( col );
2019 m_viewControlsDataModel->SetValue( i, LABEL_COLUMN, label );
2020 m_grid->SetColLabelValue( col, label );
2021
2022 if( cfg->m_FieldEditorPanel.field_widths.count( fieldNameStr ) )
2023 m_grid->SetColSize( col, cfg->m_FieldEditorPanel.field_widths.at( fieldNameStr ) );
2024
2025 // Set shown columns
2026 bool show = m_dataModel->GetShowColumn( col );
2027 m_viewControlsDataModel->SetValueAsBool( i, SHOW_FIELD_COLUMN, show );
2028
2029 if( show )
2030 m_grid->ShowCol( col );
2031 else
2032 m_grid->HideCol( col );
2033
2034 // Set grouped columns
2035 bool groupBy = m_dataModel->GetGroupColumn( col );
2036 m_viewControlsDataModel->SetValueAsBool( i, GROUP_BY_COLUMN, groupBy );
2037 }
2038
2039 m_grid->SetSortingColumn( m_dataModel->GetSortCol(), m_dataModel->GetSortAsc() );
2040 m_groupSymbolsBox->SetValue( m_dataModel->GetGroupingEnabled() );
2041 m_filter->ChangeValue( m_dataModel->GetFilter() );
2042
2044
2045 // This will rebuild all rows and columns in the model such that the order
2046 // and labels are right, then we refresh the shown grid data to match
2047 m_dataModel->EnableRebuilds();
2048 m_dataModel->RebuildRows();
2049
2050 if( m_nbPages->GetSelection() == 1 )
2052 else
2053 m_grid->ForceRefresh();
2054}
2055
2056
2057std::vector<BOM_FMT_PRESET> DIALOG_SYMBOL_FIELDS_TABLE::GetUserBomFmtPresets() const
2058{
2059 std::vector<BOM_FMT_PRESET> ret;
2060
2061 for( const auto& [name, preset] : m_bomFmtPresets )
2062 {
2063 if( !preset.readOnly )
2064 ret.emplace_back( preset );
2065 }
2066
2067 return ret;
2068}
2069
2070
2071void DIALOG_SYMBOL_FIELDS_TABLE::SetUserBomFmtPresets( std::vector<BOM_FMT_PRESET>& aPresetList )
2072{
2073 // Reset to defaults
2075
2076 for( const BOM_FMT_PRESET& preset : aPresetList )
2077 {
2078 if( m_bomFmtPresets.count( preset.name ) )
2079 continue;
2080
2081 m_bomFmtPresets[preset.name] = preset;
2082
2083 m_bomFmtPresetMRU.Add( preset.name );
2084 }
2085
2087}
2088
2089
2090void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomFmtPreset( const wxString& aPresetName )
2091{
2092 updateBomFmtPresetSelection( aPresetName );
2093
2094 wxCommandEvent dummy;
2096}
2097
2098
2100{
2101 m_currentBomFmtPreset = nullptr;
2103
2104 if( m_bomFmtPresets.count( aPreset.name ) )
2106
2109
2111 doApplyBomFmtPreset( aPreset );
2112}
2113
2114
2116{
2117 m_bomFmtPresets.clear();
2118 m_bomFmtPresetMRU.clear();
2119
2120 // Load the read-only defaults
2121 for( const BOM_FMT_PRESET& preset : BOM_FMT_PRESET::BuiltInPresets() )
2122 {
2123 m_bomFmtPresets[preset.name] = preset;
2124 m_bomFmtPresets[preset.name].readOnly = true;
2125
2126 m_bomFmtPresetMRU.Add( preset.name );
2127 }
2128}
2129
2130
2132{
2133 m_cbBomFmtPresets->Clear();
2134
2135 int idx = 0;
2136 int default_idx = 0;
2137
2138 for( const auto& [presetName, preset] : m_bomFmtPresets )
2139 {
2140 m_cbBomFmtPresets->Append( wxGetTranslation( presetName ), (void*) &preset );
2141
2142 if( presetName == BOM_FMT_PRESET::CSV().name )
2143 default_idx = idx;
2144
2145 idx++;
2146 }
2147
2148 m_cbBomFmtPresets->Append( wxT( "---" ) );
2149 m_cbBomFmtPresets->Append( _( "Save preset..." ) );
2150 m_cbBomFmtPresets->Append( _( "Delete preset..." ) );
2151
2152 // At least the built-in presets should always be present
2153 wxASSERT( !m_bomFmtPresets.empty() );
2154
2155 m_cbBomFmtPresets->SetSelection( default_idx );
2156 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( default_idx ) );
2157}
2158
2159
2161{
2163
2164 auto it = std::find_if( m_bomFmtPresets.begin(), m_bomFmtPresets.end(),
2165 [&]( const std::pair<const wxString, BOM_FMT_PRESET>& aPair )
2166 {
2167 return ( aPair.second.fieldDelimiter == current.fieldDelimiter
2168 && aPair.second.stringDelimiter == current.stringDelimiter
2169 && aPair.second.refDelimiter == current.refDelimiter
2170 && aPair.second.refRangeDelimiter == current.refRangeDelimiter
2171 && aPair.second.keepTabs == current.keepTabs
2172 && aPair.second.keepLineBreaks == current.keepLineBreaks );
2173 } );
2174
2175 if( it != m_bomFmtPresets.end() )
2176 {
2177 // Select the right m_cbBomFmtPresets item.
2178 // but these items are translated if they are predefined items.
2179 bool do_translate = it->second.readOnly;
2180 wxString text = do_translate ? wxGetTranslation( it->first ) : it->first;
2181
2182 m_cbBomFmtPresets->SetStringSelection( text );
2183 }
2184 else
2185 {
2186 m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 ); // separator
2187 }
2188
2189 int idx = m_cbBomFmtPresets->GetSelection();
2190 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
2191}
2192
2193
2195{
2196 // look at m_userBomFmtPresets to know if aName is a read only preset, or a user preset.
2197 // Read only presets have translated names in UI, so we have to use a translated name in UI selection.
2198 // But for a user preset name we should search for aName (not translated)
2199 wxString ui_label = aName;
2200
2201 for( const auto& [presetName, preset] : m_bomFmtPresets )
2202 {
2203 if( presetName == aName )
2204 {
2205 if( preset.readOnly )
2206 ui_label = wxGetTranslation( aName );
2207
2208 break;
2209 }
2210 }
2211
2212 int idx = m_cbBomFmtPresets->FindString( ui_label );
2213
2214 if( idx >= 0 && m_cbBomFmtPresets->GetSelection() != idx )
2215 {
2216 m_cbBomFmtPresets->SetSelection( idx );
2217 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
2218 }
2219 else if( idx < 0 )
2220 {
2221 m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 ); // separator
2222 }
2223}
2224
2225
2227{
2228 int count = m_cbBomFmtPresets->GetCount();
2229 int index = m_cbBomFmtPresets->GetSelection();
2230
2231 auto resetSelection =
2232 [&]()
2233 {
2235 m_cbBomFmtPresets->SetStringSelection( m_currentBomFmtPreset->name );
2236 else
2237 m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 );
2238 };
2239
2240 if( index == count - 3 )
2241 {
2242 // Separator: reject the selection
2243 resetSelection();
2244 return;
2245 }
2246 else if( index == count - 2 )
2247 {
2248 // Save current state to new preset
2249 wxString name;
2250
2253
2254 wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
2255
2256 if( dlg.ShowModal() != wxID_OK )
2257 {
2258 resetSelection();
2259 return;
2260 }
2261
2262 name = dlg.GetValue();
2263 bool exists = m_bomFmtPresets.count( name );
2264
2265 if( !exists )
2266 {
2268 m_bomFmtPresets[name].readOnly = false;
2269 m_bomFmtPresets[name].name = name;
2270 }
2271
2273
2274 if( !exists )
2275 {
2276 index = m_cbBomFmtPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
2277 }
2278 else if( preset->readOnly )
2279 {
2280 wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
2281 _( "Error" ), wxOK | wxICON_ERROR, this );
2282 resetSelection();
2283 return;
2284 }
2285 else
2286 {
2287 // Ask the user if they want to overwrite the existing preset
2288 if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
2289 {
2290 resetSelection();
2291 return;
2292 }
2293
2294 *preset = GetCurrentBomFmtSettings();
2295 preset->name = name;
2296
2297 index = m_cbBomFmtPresets->FindString( name );
2298
2299 if( m_bomFmtPresetMRU.Index( name ) != wxNOT_FOUND )
2300 m_bomFmtPresetMRU.Remove( name );
2301 }
2302
2303 m_currentBomFmtPreset = preset;
2304 m_cbBomFmtPresets->SetSelection( index );
2305 m_bomFmtPresetMRU.Insert( name, 0 );
2306
2307 return;
2308 }
2309 else if( index == count - 1 )
2310 {
2311 // Delete a preset
2312 wxArrayString headers;
2313 std::vector<wxArrayString> items;
2314
2315 headers.Add( _( "Presets" ) );
2316
2317 for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
2318 {
2319 if( !pair.second.readOnly )
2320 {
2321 wxArrayString item;
2322 item.Add( pair.first );
2323 items.emplace_back( item );
2324 }
2325 }
2326
2327 EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
2328 dlg.SetListLabel( _( "Select preset:" ) );
2329
2330 if( dlg.ShowModal() == wxID_OK )
2331 {
2332 wxString presetName = dlg.GetTextSelection();
2333 int idx = m_cbBomFmtPresets->FindString( presetName );
2334
2335 if( idx != wxNOT_FOUND )
2336 {
2337 m_bomFmtPresets.erase( presetName );
2338
2339 m_cbBomFmtPresets->Delete( idx );
2340 m_currentBomFmtPreset = nullptr;
2341 }
2342
2343 if( m_bomFmtPresetMRU.Index( presetName ) != wxNOT_FOUND )
2344 m_bomFmtPresetMRU.Remove( presetName );
2345 }
2346
2347 resetSelection();
2348 return;
2349 }
2350
2351 auto* preset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( index ) );
2352 m_currentBomFmtPreset = preset;
2353
2354 m_lastSelectedBomFmtPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
2355
2356 if( preset )
2357 {
2358 doApplyBomFmtPreset( *preset );
2360 m_currentBomFmtPreset = preset;
2361
2362 if( !m_currentBomFmtPreset->name.IsEmpty() )
2363 {
2364 if( m_bomFmtPresetMRU.Index( preset->name ) != wxNOT_FOUND )
2365 m_bomFmtPresetMRU.Remove( preset->name );
2366
2367 m_bomFmtPresetMRU.Insert( preset->name, 0 );
2368 }
2369 }
2370}
2371
2372
2374{
2375 m_textFieldDelimiter->ChangeValue( aPreset.fieldDelimiter );
2376 m_textStringDelimiter->ChangeValue( aPreset.stringDelimiter );
2377 m_textRefDelimiter->ChangeValue( aPreset.refDelimiter );
2378 m_textRefRangeDelimiter->ChangeValue( aPreset.refRangeDelimiter );
2379 m_checkKeepTabs->SetValue( aPreset.keepTabs );
2380 m_checkKeepLineBreaks->SetValue( aPreset.keepLineBreaks );
2381
2382 // Refresh the preview if that's the current page
2383 if( m_nbPages->GetSelection() == 1 )
2385}
2386
2387
2389{
2390 bool modified = false;
2391
2392 // Save our BOM presets
2393 std::vector<BOM_PRESET> presets;
2394
2395 for( const auto& [name, preset] : m_bomPresets )
2396 {
2397 if( !preset.readOnly )
2398 presets.emplace_back( preset );
2399 }
2400
2401 if( m_schSettings.m_BomPresets != presets )
2402 {
2403 modified = true;
2404 m_schSettings.m_BomPresets = presets;
2405 }
2406
2407 if( m_schSettings.m_BomSettings != m_dataModel->GetBomSettings() && !m_job )
2408 {
2409 modified = true;
2410 m_schSettings.m_BomSettings = m_dataModel->GetBomSettings();
2411 }
2412
2413 // Save our BOM Format presets
2414 std::vector<BOM_FMT_PRESET> fmts;
2415
2416 for( const auto& [name, preset] : m_bomFmtPresets )
2417 {
2418 if( !preset.readOnly )
2419 fmts.emplace_back( preset );
2420 }
2421
2422 if( m_schSettings.m_BomFmtPresets != fmts )
2423 {
2424 modified = true;
2425 m_schSettings.m_BomFmtPresets = fmts;
2426 }
2427
2428 if( m_schSettings.m_BomFmtSettings != GetCurrentBomFmtSettings() && !m_job )
2429 {
2430 modified = true;
2431 m_schSettings.m_BomFmtSettings = GetCurrentBomFmtSettings();
2432 }
2433
2434 if( modified )
2435 m_parent->OnModify();
2436}
2437
2438
2439void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsAdded( SCHEMATIC& aSch, std::vector<SCH_ITEM*>& aSchItem )
2440{
2441 std::set<wxString> savedSelection = SaveGridSelection();
2442
2443 SCH_REFERENCE_LIST allRefs;
2444 m_parent->Schematic().Hierarchy().GetSymbols( allRefs );
2445
2446 for( SCH_ITEM* item : aSchItem )
2447 {
2448 if( item->Type() == SCH_SYMBOL_T )
2449 {
2450 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2451
2452 // Don't add power symbols
2453 if( !symbol->IsMissingLibSymbol() && symbol->IsPower() )
2454 continue;
2455
2456 // Add all fields again in case this symbol has a new one
2457 for( SCH_FIELD& field : symbol->GetFields() )
2458 AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
2459
2460 m_dataModel->AddReferences( getSymbolReferences( symbol, allRefs ) );
2461 }
2462 else if( item->Type() == SCH_SHEET_T )
2463 {
2464 std::set<SCH_SYMBOL*> symbols;
2465 SCH_REFERENCE_LIST refs = getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) );
2466
2467 for( SCH_REFERENCE& ref : refs )
2468 symbols.insert( ref.GetSymbol() );
2469
2470 for( SCH_SYMBOL* symbol : symbols )
2471 {
2472 // Add all fields again in case this symbol has a new one
2473 for( SCH_FIELD& field : symbol->GetFields() )
2474 AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
2475 }
2476
2477 m_dataModel->AddReferences( refs );
2478 }
2479 }
2480
2482 m_dataModel->RebuildRows();
2483 RestoreGridSelection( savedSelection );
2485}
2486
2487
2488void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsRemoved( SCHEMATIC& aSch, std::vector<SCH_ITEM*>& aSchItem )
2489{
2490 std::set<wxString> savedSelection = SaveGridSelection();
2491
2492 for( SCH_ITEM* item : aSchItem )
2493 {
2494 if( item->Type() == SCH_SYMBOL_T )
2495 m_dataModel->RemoveSymbol( *static_cast<SCH_SYMBOL*>( item ) );
2496 else if( item->Type() == SCH_SHEET_T )
2497 m_dataModel->RemoveReferences( getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) ) );
2498 }
2499
2501 m_dataModel->RebuildRows();
2502 RestoreGridSelection( savedSelection );
2504}
2505
2506
2507void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsChanged( SCHEMATIC& aSch, std::vector<SCH_ITEM*>& aSchItem )
2508{
2509 std::set<wxString> savedSelection = SaveGridSelection();
2510
2511 SCH_REFERENCE_LIST allRefs;
2512 m_parent->Schematic().Hierarchy().GetSymbols( allRefs );
2513
2514 for( SCH_ITEM* item : aSchItem )
2515 {
2516 if( item->Type() == SCH_SYMBOL_T )
2517 {
2518 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2519
2520 // Don't add power symbols
2521 if( !symbol->IsMissingLibSymbol() && symbol->IsPower() )
2522 continue;
2523
2524 // Add all fields again in case this symbol has a new one
2525 for( SCH_FIELD& field : symbol->GetFields() )
2526 AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
2527
2528 m_dataModel->UpdateReferences( getSymbolReferences( symbol, allRefs ),
2529 m_parent->Schematic().GetCurrentVariant() );
2530 }
2531 else if( item->Type() == SCH_SHEET_T )
2532 {
2533 std::set<SCH_SYMBOL*> symbols;
2534 SCH_REFERENCE_LIST refs = getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) );
2535
2536 for( SCH_REFERENCE& ref : refs )
2537 symbols.insert( ref.GetSymbol() );
2538
2539 for( SCH_SYMBOL* symbol : symbols )
2540 {
2541 // Add all fields again in case this symbol has a new one
2542 for( SCH_FIELD& field : symbol->GetFields() )
2543 AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
2544 }
2545
2546 m_dataModel->UpdateReferences( refs, m_parent->Schematic().GetCurrentVariant() );
2547 }
2548 }
2549
2551 m_dataModel->RebuildRows();
2552 RestoreGridSelection( savedSelection );
2554}
2555
2556
2558{
2559 m_dataModel->SetPath( aSch.CurrentSheet() );
2560
2561 if( m_dataModel->GetScope() != FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_ALL )
2562 {
2563 std::set<wxString> savedSelection = SaveGridSelection();
2564
2566 m_dataModel->RebuildRows();
2567 RestoreGridSelection( savedSelection );
2569 }
2570}
2571
2572
2574{
2575 m_grid->Connect( wxEVT_GRID_RANGE_SELECTED,
2576 wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected ),
2577 nullptr, this );
2578}
2579
2580
2582{
2583 m_grid->Disconnect( wxEVT_GRID_RANGE_SELECTED,
2584 wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected ),
2585 nullptr, this );
2586}
2587
2588
2590{
2591 std::set<wxString> selectedFullPaths;
2592
2593 wxGridCellCoordsArray topLeft = m_grid->GetSelectionBlockTopLeft();
2594 wxGridCellCoordsArray bottomRight = m_grid->GetSelectionBlockBottomRight();
2595
2596 for( size_t i = 0; i < topLeft.size(); ++i )
2597 {
2598 for( int row = topLeft[i].GetRow(); row <= bottomRight[i].GetRow(); ++row )
2599 {
2600 for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( row ) )
2601 selectedFullPaths.insert( ref.GetFullPath() );
2602 }
2603 }
2604
2605 wxArrayInt selectedRows = m_grid->GetSelectedRows();
2606
2607 for( int row : selectedRows )
2608 {
2609 for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( row ) )
2610 selectedFullPaths.insert( ref.GetFullPath() );
2611 }
2612
2613 int cursorRow = m_grid->GetGridCursorRow();
2614
2615 if( cursorRow >= 0 && selectedFullPaths.empty() )
2616 {
2617 for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( cursorRow ) )
2618 selectedFullPaths.insert( ref.GetFullPath() );
2619 }
2620
2621 return selectedFullPaths;
2622}
2623
2624
2625void DIALOG_SYMBOL_FIELDS_TABLE::RestoreGridSelection( const std::set<wxString>& aFullPaths )
2626{
2627 if( aFullPaths.empty() )
2628 return;
2629
2630 m_grid->ClearSelection();
2631
2632 bool firstSelection = true;
2633
2634 for( int row = 0; row < m_dataModel->GetNumberRows(); ++row )
2635 {
2636 std::vector<SCH_REFERENCE> refs = m_dataModel->GetRowReferences( row );
2637
2638 for( const SCH_REFERENCE& ref : refs )
2639 {
2640 if( aFullPaths.count( ref.GetFullPath() ) )
2641 {
2642 m_grid->SelectRow( row, true );
2643
2644 if( firstSelection )
2645 {
2646 m_grid->SetGridCursor( row, m_grid->GetGridCursorCol() );
2647 firstSelection = false;
2648 }
2649
2650 break;
2651 }
2652 }
2653 }
2654}
2655
2656
2658 SCH_REFERENCE_LIST& aCachedRefs )
2659{
2660 SCH_REFERENCE_LIST symbolRefs;
2661
2662 for( size_t i = 0; i < aCachedRefs.GetCount(); i++ )
2663 {
2664 SCH_REFERENCE& ref = aCachedRefs[i];
2665
2666 if( ref.GetSymbol() == aSymbol )
2667 {
2668 ref.Split(); // Figures out if we are annotated or not
2669 symbolRefs.AddItem( ref );
2670 }
2671 }
2672
2673 return symbolRefs;
2674}
2675
2676
2678{
2679 SCH_SHEET_LIST allSheets = m_parent->Schematic().Hierarchy();
2680 SCH_REFERENCE_LIST sheetRefs;
2681
2682 // We need to operate on all instances of the sheet
2683 for( const SCH_SHEET_INSTANCE& instance : aSheet.GetInstances() )
2684 {
2685 // For every sheet instance we need to get the current schematic sheet
2686 // instance that matches that particular sheet path from the root
2687 for( SCH_SHEET_PATH& basePath : allSheets )
2688 {
2689 if( basePath.Path() == instance.m_Path )
2690 {
2691 SCH_SHEET_PATH sheetPath = basePath;
2692 sheetPath.push_back( &aSheet );
2693
2694 // Create a list of all sheets in this path, starting with the path
2695 // of the sheet that we just deleted, then all of its subsheets
2696 SCH_SHEET_LIST subSheets;
2697 subSheets.push_back( sheetPath );
2698 allSheets.GetSheetsWithinPath( subSheets, sheetPath );
2699
2700 subSheets.GetSymbolsWithinPath( sheetRefs, sheetPath, false, false );
2701 break;
2702 }
2703 }
2704 }
2705
2706 for( SCH_REFERENCE& ref : sheetRefs )
2707 ref.Split();
2708
2709 return sheetRefs;
2710}
2711
2712
2713void DIALOG_SYMBOL_FIELDS_TABLE::onAddVariant( wxCommandEvent& aEvent )
2714{
2715 if( !m_parent->ShowAddVariantDialog() )
2716 return;
2717
2718 wxArrayString ctrlContents;
2719 ctrlContents.Add( GetDefaultVariantName() );
2720
2721 for( const wxString& variant : m_parent->Schematic().GetVariantNames() )
2722 ctrlContents.Add( variant );
2723
2724 ctrlContents.Sort( SortVariantNames );
2725 m_variantListBox->Set( ctrlContents );
2726
2727 wxString currentVariant = m_parent->Schematic().GetCurrentVariant();
2728 int newSelection = m_variantListBox->FindString(
2729 currentVariant.IsEmpty() ? GetDefaultVariantName() : currentVariant );
2730
2731 if( newSelection != wxNOT_FOUND )
2732 m_variantListBox->SetSelection( newSelection );
2733
2735}
2736
2737
2739{
2740 int selection = m_variantListBox->GetSelection();
2741
2742 // An empty or default selection cannot be deleted.
2743 if( ( selection == wxNOT_FOUND ) || ( selection == 0 ) )
2744 {
2745 m_parent->GetInfoBar()->ShowMessageFor( _( "Cannot delete the default variant." ),
2746 10000, wxICON_ERROR );
2747 return;
2748 }
2749
2750 wxString variantName = m_variantListBox->GetString( selection );
2751 m_variantListBox->Delete( selection );
2752 m_parent->Schematic().DeleteVariant( variantName );
2753
2754 int newSelection = std::max( 0, selection - 1 );
2755 m_variantListBox->SetSelection( newSelection );
2756
2757 wxString selectedVariant = getSelectedVariant();
2758 m_parent->SetCurrentVariant( selectedVariant );
2759
2760 if( m_grid->CommitPendingChanges( true ) )
2761 {
2762 m_dataModel->SetCurrentVariant( selectedVariant );
2763 m_dataModel->UpdateReferences( m_dataModel->GetReferenceList(), selectedVariant );
2764 m_dataModel->RebuildRows();
2765
2766 if( m_nbPages->GetSelection() == 1 )
2768 else
2769 m_grid->ForceRefresh();
2770 }
2771
2773 m_parent->UpdateVariantSelectionCtrl( m_parent->Schematic().GetVariantNamesForUI() );
2774}
2775
2776
2778{
2779 int selection = m_variantListBox->GetSelection();
2780
2781 // An empty or default selection cannot be renamed.
2782 if( ( selection == wxNOT_FOUND ) || ( selection == 0 ) )
2783 {
2784 m_parent->GetInfoBar()->ShowMessageFor( _( "Cannot rename the default variant." ),
2785 10000, wxICON_ERROR );
2786 return;
2787 }
2788
2789 wxString oldVariantName = m_variantListBox->GetString( selection );
2790
2791 wxTextEntryDialog dlg( this, _( "Enter new variant name:" ), _( "Rename Design Variant" ),
2792 oldVariantName, wxOK | wxCANCEL | wxCENTER );
2793
2794 if( dlg.ShowModal() == wxID_CANCEL )
2795 return;
2796
2797 wxString newVariantName = dlg.GetValue().Trim().Trim( false );
2798
2799 // Empty name is not allowed.
2800 if( newVariantName.IsEmpty() )
2801 {
2802 m_parent->GetInfoBar()->ShowMessageFor( _( "Variant name cannot be empty." ), 10000, wxICON_ERROR );
2803 return;
2804 }
2805
2806 // Reserved name is not allowed (case-insensitive).
2807 if( newVariantName.CmpNoCase( GetDefaultVariantName() ) == 0 )
2808 {
2809 m_parent->GetInfoBar()->ShowMessageFor( wxString::Format( _( "'%s' is a reserved variant name." ),
2811 10000, wxICON_ERROR );
2812 return;
2813 }
2814
2815 // Same name (exact match) - nothing to do
2816 if( newVariantName == oldVariantName )
2817 return;
2818
2819 // Duplicate name is not allowed (case-insensitive).
2820 for( const wxString& existingName : m_parent->Schematic().GetVariantNames() )
2821 {
2822 if( existingName.CmpNoCase( newVariantName ) == 0
2823 && existingName.CmpNoCase( oldVariantName ) != 0 )
2824 {
2825 m_parent->GetInfoBar()->ShowMessageFor( wxString::Format( _( "Variant '%s' already exists." ),
2826 existingName ),
2827 0000, wxICON_ERROR );
2828 return;
2829 }
2830 }
2831
2832 m_parent->Schematic().RenameVariant( oldVariantName, newVariantName );
2833
2834 wxArrayString ctrlContents = m_variantListBox->GetStrings();
2835 ctrlContents.Remove( oldVariantName );
2836 ctrlContents.Add( newVariantName );
2837 ctrlContents.Sort( SortVariantNames );
2838 m_variantListBox->Set( ctrlContents );
2839
2840 int newSelection = m_variantListBox->FindString( newVariantName );
2841
2842 if( newSelection != wxNOT_FOUND )
2843 m_variantListBox->SetSelection( newSelection );
2844
2846 m_parent->UpdateVariantSelectionCtrl( m_parent->Schematic().GetVariantNamesForUI() );
2847}
2848
2849
2850void DIALOG_SYMBOL_FIELDS_TABLE::onCopyVariant( wxCommandEvent& aEvent )
2851{
2852 int selection = m_variantListBox->GetSelection();
2853
2854 // An empty or default selection cannot be copied.
2855 if( ( selection == wxNOT_FOUND ) || ( selection == 0 ) )
2856 {
2857 m_parent->GetInfoBar()->ShowMessageFor( _( "Cannot copy the default variant." ),
2858 10000, wxICON_ERROR );
2859 return;
2860 }
2861
2862 wxString sourceVariantName = m_variantListBox->GetString( selection );
2863
2864 wxTextEntryDialog dlg( this, _( "Enter name for the copied variant:" ), _( "Copy Design Variant" ),
2865 sourceVariantName + wxS( "_copy" ), wxOK | wxCANCEL | wxCENTER );
2866
2867 if( dlg.ShowModal() == wxID_CANCEL )
2868 return;
2869
2870 wxString newVariantName = dlg.GetValue().Trim().Trim( false );
2871
2872 // Empty name is not allowed.
2873 if( newVariantName.IsEmpty() )
2874 {
2875 m_parent->GetInfoBar()->ShowMessageFor( _( "Variant name cannot be empty." ), 10000, wxICON_ERROR );
2876 return;
2877 }
2878
2879 // Duplicate name is not allowed.
2880 if( m_variantListBox->FindString( newVariantName ) != wxNOT_FOUND )
2881 {
2882 m_parent->GetInfoBar()->ShowMessageFor( wxString::Format( _( "Variant '%s' already exists." ),
2883 newVariantName ),
2884 10000, wxICON_ERROR );
2885 return;
2886 }
2887
2888 m_parent->Schematic().CopyVariant( sourceVariantName, newVariantName );
2889
2890 wxArrayString ctrlContents = m_variantListBox->GetStrings();
2891 ctrlContents.Add( newVariantName );
2892 ctrlContents.Sort( SortVariantNames );
2893 m_variantListBox->Set( ctrlContents );
2894
2895 int newSelection = m_variantListBox->FindString( newVariantName );
2896
2897 if( newSelection != wxNOT_FOUND )
2898 m_variantListBox->SetSelection( newSelection );
2899
2901 m_parent->UpdateVariantSelectionCtrl( m_parent->Schematic().GetVariantNamesForUI() );
2902}
2903
2904
2906{
2907 wxString currentVariant;
2908 wxString selectedVariant = getSelectedVariant();
2909
2911
2912 if( m_parent )
2913 {
2914 currentVariant = m_parent->Schematic().GetCurrentVariant();
2915
2916 if( currentVariant != selectedVariant )
2917 m_parent->SetCurrentVariant( selectedVariant );
2918 }
2919
2920 if( currentVariant != selectedVariant )
2921 {
2922 m_grid->CommitPendingChanges( true );
2923
2924 SCH_COMMIT commit( m_parent );
2925
2926 m_dataModel->ApplyData( commit, m_schSettings.m_TemplateFieldNames, currentVariant );
2927
2928 if( !commit.Empty() )
2929 {
2930 commit.Push( wxS( "Symbol Fields Table Edit" ) ); // Push clears the commit buffer.
2931 m_parent->OnModify();
2932 }
2933
2934 // Update the data model's current variant for field highlighting
2935 m_dataModel->SetCurrentVariant( selectedVariant );
2936 m_dataModel->UpdateReferences( m_dataModel->GetReferenceList(), selectedVariant );
2937 m_dataModel->RebuildRows();
2938
2939 if( m_nbPages->GetSelection() == 1 )
2941 else
2942 m_grid->ForceRefresh();
2943
2945 }
2946}
2947
2948
2950{
2951 int selection = m_variantListBox->GetSelection();
2952
2953 // Copy, rename, and delete are only enabled for non-default variant selections
2954 bool canModify = ( selection != wxNOT_FOUND ) && ( selection != 0 );
2955
2956 m_copyVariantButton->Enable( canModify );
2957 m_renameVariantButton->Enable( canModify );
2958 m_deleteVariantButton->Enable( canModify );
2959}
2960
2961
2963{
2964 wxString retv;
2965
2966 int selection = m_variantListBox->GetSelection();
2967
2968 if( ( selection == wxNOT_FOUND ) || ( m_variantListBox->GetString( selection ) == GetDefaultVariantName() ) )
2969 return retv;
2970
2971 return m_variantListBox->GetString( selection );
2972}
int index
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:110
bool Empty() const
Definition commit.h:137
int vertPixelsFromDU(int y) const
Convert an integer number of dialog units to pixels, vertically.
void SetInitialFocus(wxWindow *aWindow)
Sets the window (usually a wxTextCtrl) that should be focused when the dialog is shown.
Definition dialog_shim.h:82
void SetupStandardButtons(std::map< int, wxString > aLabels={})
std::string m_hash_key
int horizPixelsFromDU(int x) const
Convert an integer number of dialog units to pixels, horizontally.
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
int ShowModal() override
DIALOG_SYMBOL_FIELDS_TABLE_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Symbol Fields Table"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxRESIZE_BORDER)
void OnTableColSize(wxGridSizeEvent &event) override
void OnSaveAndContinue(wxCommandEvent &aEvent) override
void OnSchItemsRemoved(SCHEMATIC &aSch, std::vector< SCH_ITEM * > &aSchItem) override
void onAddVariant(wxCommandEvent &aEvent) override
void OnPreviewRefresh(wxCommandEvent &event) override
void OnAddField(wxCommandEvent &event) override
SCH_REFERENCE_LIST getSheetSymbolReferences(SCH_SHEET &aSheet)
void SetUserBomPresets(std::vector< BOM_PRESET > &aPresetList)
void OnSidebarToggle(wxCommandEvent &event) override
void OnOk(wxCommandEvent &aEvent) override
void OnGroupSymbolsToggled(wxCommandEvent &event) override
void OnSchItemsAdded(SCHEMATIC &aSch, std::vector< SCH_ITEM * > &aSchItem) override
std::map< wxString, BOM_PRESET > m_bomPresets
void OnSchItemsChanged(SCHEMATIC &aSch, std::vector< SCH_ITEM * > &aSchItem) override
void ApplyBomFmtPreset(const wxString &aPresetName)
void ShowHideColumn(int aCol, bool aShow)
VIEW_CONTROLS_GRID_DATA_MODEL * m_viewControlsDataModel
void setSideBarButtonLook(bool aIsLeftPanelCollapsed)
FIELDS_EDITOR_GRID_DATA_MODEL * m_dataModel
void updateBomPresetSelection(const wxString &aName)
void updateBomFmtPresetSelection(const wxString &aName)
void OnFilterText(wxCommandEvent &aEvent) override
std::map< FIELD_T, int > m_mandatoryFieldListIndexes
void OnRemoveField(wxCommandEvent &event) override
void OnTableCellClick(wxGridEvent &event) override
void onVariantSelectionChange(wxCommandEvent &aEvent) override
void doApplyBomFmtPreset(const BOM_FMT_PRESET &aPreset)
void RestoreGridSelection(const std::set< wxString > &aFullPaths)
Restores the grid selection from a previously saved set of symbol full paths.
void OnScope(wxCommandEvent &event) override
void onBomPresetChanged(wxCommandEvent &aEvent)
void OnTableValueChanged(wxGridEvent &event) override
void OnExport(wxCommandEvent &aEvent) override
void OnClose(wxCloseEvent &aEvent) override
void OnSchSheetChanged(SCHEMATIC &aSch) override
void onCopyVariant(wxCommandEvent &aEvent) override
void onDeleteVariant(wxCommandEvent &aEvent) override
void OnTableRangeSelected(wxGridRangeSelectEvent &aEvent)
void OnMenu(wxCommandEvent &event) override
void setScope(FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE aScope)
std::vector< BOM_FMT_PRESET > GetUserBomFmtPresets() const
void OnCancel(wxCommandEvent &aEvent) override
void OnFilterMouseMoved(wxMouseEvent &event) override
std::map< wxString, BOM_FMT_PRESET > m_bomFmtPresets
DIALOG_SYMBOL_FIELDS_TABLE(SCH_EDIT_FRAME *parent, JOB_EXPORT_SCH_BOM *aJob=nullptr)
BOM_FMT_PRESET GetCurrentBomFmtSettings()
Returns a formatting configuration corresponding to the values in the UI controls of the dialog.
std::set< wxString > SaveGridSelection()
Saves the current grid selection as a set of symbol full paths for later restoration.
void AddField(const wxString &displayName, const wxString &aCanonicalName, bool show, bool groupBy, bool addedByUser=false)
SCH_REFERENCE_LIST getSymbolReferences(SCH_SYMBOL *aSymbol, SCH_REFERENCE_LIST &aCachedRefs)
void doApplyBomPreset(const BOM_PRESET &aPreset)
void OnPageChanged(wxNotebookEvent &event) override
void SetUserBomFmtPresets(std::vector< BOM_FMT_PRESET > &aPresetList)
void OnRegroupSymbols(wxCommandEvent &aEvent) override
void OnViewControlsCellChanged(wxGridEvent &aEvent) override
void onRenameVariant(wxCommandEvent &aEvent) override
std::vector< BOM_PRESET > GetUserBomPresets() const
void OnOutputFileBrowseClicked(wxCommandEvent &event) override
void LoadFieldNames()
Construct the rows of m_fieldsCtrl and the columns of m_dataModel from a union of all field names in ...
void onBomFmtPresetChanged(wxCommandEvent &aEvent)
void ApplyBomPreset(const wxString &aPresetName)
void OnSizeViewControlsGrid(wxSizeEvent &event) override
void OnRenameField(wxCommandEvent &event) override
A base class for most all the KiCad significant classes used in schematics and boards.
Definition eda_item.h:99
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:111
EDA_ITEM * GetParent() const
Definition eda_item.h:113
A dialog which shows:
wxString GetTextSelection(int aColumn=0)
Return the selected text from aColumn in the wxListCtrl in the dialog.
void SetListLabel(const wxString &aLabel)
PANEL_SYMBOL_FIELDS_TABLE m_FieldEditorPanel
static const wxString ITEM_NUMBER_VARIABLE
static const wxString QUANTITY_VARIABLE
VIEW_CONTROLS_GRID_DATA_MODEL * m_viewControlsDataModel
DIALOG_SYMBOL_FIELDS_TABLE * m_dlg
void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent) override
void doPopupSelection(wxCommandEvent &event) override
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)
FIELDS_EDITOR_GRID_DATA_MODEL * m_dataModel
A general-purpose text renderer for WX_GRIDs backed by WX_GRID_TABLE_BASE tables that can handle draw...
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition grid_tricks.h:61
GRID_TRICKS(WX_GRID *aGrid)
virtual void doPopupSelection(wxCommandEvent &event)
virtual void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent)
WX_GRID * m_grid
I don't own the grid, but he owns me.
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
A wxFrame capable of the OpenProjectFiles function, meaning it can load a portion of a KiCad project.
static REPORTER & GetInstance()
Definition reporter.cpp:97
static SEARCH_STACK * SchSearchS(PROJECT *aProject)
Accessor for Eeschema search stack.
virtual const wxString AbsolutePath(const wxString &aFileName) const
Fix up aFileName if it is relative to the project's directory to be an absolute path and filename.
Definition project.cpp:401
Holds all the data relating to one schematic.
Definition schematic.h:88
bool ResolveTextVar(const SCH_SHEET_PATH *aSheetPath, wxString *token, int aDepth) const
wxArrayString GetVariantNamesForUI() const
Return an array of variant names for using in wxWidgets UI controls.
SCH_SHEET_PATH & CurrentSheet() const
Definition schematic.h:187
virtual void Push(const wxString &aMessage=wxT("A commit"), int aCommitFlags=0) override
Execute the changes.
Handle actions specific to the schematic editor.
Schematic editor (Eeschema) main window.
SCHEMATIC & Schematic() const
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition sch_item.h:168
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
void AddItem(const SCH_REFERENCE &aItem)
A helper to define a symbol's reference designator in a schematic.
void Split()
Attempt to split the reference designator into a name (U) and number (1).
SCH_SYMBOL * GetSymbol() const
void SyncSelection(const std::optional< SCH_SHEET_PATH > &targetSheetPath, SCH_ITEM *focusItem, const std::vector< SCH_ITEM * > &items)
int ClearSelection(const TOOL_EVENT &aEvent)
Select all visible items in sheet.
SCH_SELECTION & GetSelection()
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
void GetSheetsWithinPath(std::vector< SCH_SHEET_PATH > &aSheets, const SCH_SHEET_PATH &aSheetPath) const
Add a SCH_SHEET_PATH object to aSheets for each sheet in the list that are contained within aSheetPat...
void GetSymbolsWithinPath(SCH_REFERENCE_LIST &aReferences, const SCH_SHEET_PATH &aSheetPath, bool aIncludePowerSymbols=true, bool aForceIncludeOrphanSymbols=false) const
Add a SCH_REFERENCE object to aReferences for each symbol in the list of sheets that are contained wi...
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
void push_back(SCH_SHEET *aSheet)
Forwarded method from std::vector.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
const std::vector< SCH_SHEET_INSTANCE > & GetInstances() const
Definition sch_sheet.h:511
Schematic symbol object.
Definition sch_symbol.h:76
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly) const override
Populate a std::vector with SCH_FIELDs, sorted in ordinal order.
bool IsMissingLibSymbol() const
Check to see if the library symbol is set to the dummy library symbol.
bool IsPower() const override
Master controller class:
void doPopupSelection(wxCommandEvent &event) override
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:558
wxString GetGeneratedFieldDisplayName(const wxString &aSource)
Returns any variables unexpanded, e.g.
Definition common.cpp:323
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, int aFlags)
Definition common.cpp:62
bool EnsureFileDirectoryExists(wxFileName *aTargetFullFileName, const wxString &aBaseFilename, REPORTER *aReporter)
Make aTargetFullFileName absolute and create the path of this file if it doesn't yet exist.
Definition common.cpp:579
bool IsGeneratedField(const wxString &aSource)
Returns true if the string is generated, e.g contains a single text var reference.
Definition common.cpp:335
The common library.
int OKOrCancelDialog(wxWindow *aParent, const wxString &aWarning, const wxString &aMessage, const wxString &aDetailedMessage, const wxString &aOKLabel, const wxString &aCancelLabel, bool *aApplyToAll)
Display a warning dialog with aMessage and returns the user response.
Definition confirm.cpp:150
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition confirm.cpp:259
void DisplayInfoMessage(wxWindow *aParent, const wxString &aMessage, const wxString &aExtraInfo)
Display an informational message box with aMessage.
Definition confirm.cpp:230
bool HandleUnsavedChanges(wxWindow *aParent, const wxString &aMessage, const std::function< bool()> &aSaveFunction)
Display a dialog with Save, Cancel and Discard Changes buttons.
Definition confirm.cpp:131
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:202
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition confirm.cpp:177
This file is part of the common library.
#define COLUMN_MARGIN
@ MYID_SELECT_FOOTPRINT
wxDEFINE_EVENT(EDA_EVT_CLOSE_DIALOG_SYMBOL_FIELDS_TABLE, wxCommandEvent)
FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE SCOPE
#define _(s)
bool GetAssociatedDocument(wxWindow *aParent, const wxString &aDocName, PROJECT *aProject, SEARCH_STACK *aPaths, std::vector< EMBEDDED_FILES * > aFilesStack)
Open a document (file) with the suitable browser.
Definition eda_doc.cpp:63
This file is part of the common library.
#define LABEL_COLUMN
#define DISPLAY_NAME_COLUMN
#define GROUP_BY_COLUMN
#define SHOW_FIELD_COLUMN
@ FRAME_FOOTPRINT_CHOOSER
Definition frame_type.h:44
@ GRIDTRICKS_FIRST_SHOWHIDE
Definition grid_tricks.h:51
@ GRIDTRICKS_FIRST_CLIENT_ID
Definition grid_tricks.h:48
static const std::string CsvFileExtension
static wxString CsvFileWildcard()
void AllowNetworkFileSystems(wxDialog *aDialog)
Configure a file dialog to show network and virtual file systems.
Definition wxgtk/ui.cpp:435
KICOMMON_API wxSize GetTextSize(const wxString &aSingleLine, wxWindow *aWindow)
Return the size of aSingleLine of text when it is rendered in aWindow using whatever font is currentl...
Definition ui_common.cpp:78
bool contains(const _Container &__container, _Value __value)
Returns true if the container contains the given value.
Definition kicad_algo.h:100
@ HIGHLIGHT_SYMBOL
std::vector< FAB_LAYER_COLOR > dummy
wxString GetDefaultVariantName()
int SortVariantNames(const wxString &aLhs, const wxString &aRhs)
#define TO_UTF8(wxstring)
Convert a wxString to a UTF8 encoded C string for all wxWidgets build modes.
wxString label
wxString name
wxString fieldDelimiter
static BOM_FMT_PRESET CSV()
static std::vector< BOM_FMT_PRESET > BuiltInPresets()
wxString stringDelimiter
wxString refRangeDelimiter
wxString refDelimiter
wxString name
static BOM_PRESET DefaultEditing()
wxString sortField
bool groupSymbols
std::vector< BOM_FIELD > fieldsOrdered
static std::vector< BOM_PRESET > BuiltInPresets()
bool excludeDNP
wxString filterString
A simple container for sheet instance information.
Hold a name of a symbol's field, field value, and default visibility.
wxString GetDefaultFieldName(FIELD_T aFieldId, bool aTranslateForHI)
Return a default symbol field name for a mandatory field type.
#define DO_TRANSLATE
#define MANDATORY_FIELDS
FIELD_T
The set of all field indices assuming an array like sequence that a SCH_COMPONENT or LIB_PART can hol...
@ DESCRIPTION
Field Description of part, i.e. "1/4W 1% Metal Film Resistor".
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ DATASHEET
name of datasheet
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
wxString GetCanonicalFieldName(FIELD_T aFieldType)
std::string path
@ SCH_SYMBOL_T
Definition typeinfo.h:176
@ SCH_SHEET_T
Definition typeinfo.h:179
Definition of file extensions used in Kicad.