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 );
327 m_grid->GetGridWindow()->Bind( wxEVT_MOTION, &DIALOG_SYMBOL_FIELDS_TABLE::OnGridMouseMove, this );
330 m_viewControlsGrid->Bind( wxEVT_GRID_CELL_CHANGED, &DIALOG_SYMBOL_FIELDS_TABLE::OnViewControlsCellChanged, this );
331
332 if( !m_job )
333 {
334 // Start listening for schematic changes
335 m_parent->Schematic().AddListener( this );
336 }
337 else
338 {
339 // Don't allow editing
340 m_grid->EnableEditing( false );
341 m_buttonApply->Hide();
342 m_buttonExport->Hide();
343 }
344}
345
346
348{
350 m_schSettings.m_BomExportFileName = m_outputFileName->GetValue();
351
352 EESCHEMA_SETTINGS::PANEL_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
353
354 if( !cfg.sidebar_collapsed )
355 cfg.sash_pos = m_splitterMainWindow->GetSashPosition();
356
357 cfg.variant_sash_pos = m_splitter_left->GetSashPosition();
358
359 for( int i = 0; i < m_grid->GetNumberCols(); i++ )
360 {
361 if( m_grid->IsColShown( i ) )
362 {
363 std::string fieldName( m_dataModel->GetColFieldName( i ).ToUTF8() );
364 cfg.field_widths[fieldName] = m_grid->GetColSize( i );
365 }
366 }
367
368 // Disconnect Events
369 m_grid->GetGridWindow()->Unbind( wxEVT_MOTION, &DIALOG_SYMBOL_FIELDS_TABLE::OnGridMouseMove, this );
370 m_grid->Unbind( wxEVT_GRID_COL_SORT, &DIALOG_SYMBOL_FIELDS_TABLE::OnColSort, this );
371 m_grid->Unbind( wxEVT_GRID_COL_SORT, &DIALOG_SYMBOL_FIELDS_TABLE::OnColMove, this );
374 m_viewControlsGrid->Unbind( wxEVT_GRID_CELL_CHANGED, &DIALOG_SYMBOL_FIELDS_TABLE::OnViewControlsCellChanged, this );
375
376 // Delete the GRID_TRICKS.
377 m_viewControlsGrid->PopEventHandler( true );
378 m_grid->PopEventHandler( true );
379
380 // we gave ownership of m_viewControlsDataModel & m_dataModel to the wxGrids...
381}
382
383
384void DIALOG_SYMBOL_FIELDS_TABLE::setSideBarButtonLook( bool aIsLeftPanelCollapsed )
385{
386 // Set bitmap and tooltip according to left panel visibility
387
388 if( aIsLeftPanelCollapsed )
389 {
391 m_sidebarButton->SetToolTip( _( "Expand left panel" ) );
392 }
393 else
394 {
396 m_sidebarButton->SetToolTip( _( "Collapse left panel" ) );
397 }
398}
399
400
402{
403 wxGridCellAttr* attr = new wxGridCellAttr;
404 attr->SetReadOnly( false );
405
406 // Set some column types to specific editors
407 if( m_dataModel->ColIsReference( aCol ) )
408 {
409 attr->SetReadOnly();
410 attr->SetRenderer( new GRID_CELL_TEXT_RENDERER() );
411 m_dataModel->SetColAttr( attr, aCol );
412 }
413 else if( m_dataModel->GetColFieldName( aCol ) == GetCanonicalFieldName( FIELD_T::FOOTPRINT ) )
414 {
415 attr->SetEditor( new GRID_CELL_FPID_EDITOR( this, wxEmptyString ) );
416 m_dataModel->SetColAttr( attr, aCol );
417 }
418 else if( m_dataModel->GetColFieldName( aCol ) == GetCanonicalFieldName( FIELD_T::DATASHEET ) )
419 {
420 // set datasheet column viewer button
421 attr->SetEditor( new GRID_CELL_URL_EDITOR( this, PROJECT_SCH::SchSearchS( &Prj() ),
422 { &m_parent->Schematic() } ) );
423 m_dataModel->SetColAttr( attr, aCol );
424 }
425 else if( m_dataModel->ColIsQuantity( aCol ) || m_dataModel->ColIsItemNumber( aCol ) )
426 {
427 attr->SetReadOnly();
428 attr->SetAlignment( wxALIGN_RIGHT, wxALIGN_CENTER );
429 attr->SetRenderer( new wxGridCellNumberRenderer() );
430 m_dataModel->SetColAttr( attr, aCol );
431 }
432 else if( m_dataModel->ColIsAttribute( aCol ) )
433 {
434 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
435 attr->SetRenderer( new GRID_CELL_CHECKBOX_RENDERER() );
436 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
437 m_dataModel->SetColAttr( attr, aCol );
438 }
439 else if( IsGeneratedField( m_dataModel->GetColFieldName( aCol ) ) )
440 {
441 attr->SetReadOnly();
442 m_dataModel->SetColAttr( attr, aCol );
443 }
444 else
445 {
446 attr->SetRenderer( new GRID_CELL_TEXT_RENDERER() );
447 attr->SetEditor( m_grid->GetDefaultEditor() );
448 m_dataModel->SetColAttr( attr, aCol );
449 }
450}
451
452
454{
455 EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
456 wxSize defaultDlgSize = ConvertDialogToPixels( wxSize( 600, 300 ) );
457
458 // Restore column sorting order and widths
459 m_grid->AutoSizeColumns( false );
460 int sortCol = 0;
461 bool sortAscending = true;
462
463 for( int col = 0; col < m_grid->GetNumberCols(); ++col )
464 {
466
467 if( col == m_dataModel->GetSortCol() )
468 {
469 sortCol = col;
470 sortAscending = m_dataModel->GetSortAsc();
471 }
472 }
473
474 // sync m_grid's column visibilities to Show checkboxes in m_viewControlsGrid
475 for( int i = 0; i < m_viewControlsDataModel->GetNumberRows(); ++i )
476 {
477 int col = m_dataModel->GetFieldNameCol( m_viewControlsDataModel->GetCanonicalFieldName( i ) );
478
479 if( col == -1 )
480 continue;
481
482 bool show = m_viewControlsDataModel->GetValueAsBool( i, SHOW_FIELD_COLUMN );
483 m_dataModel->SetShowColumn( col, show );
484
485 if( show )
486 {
487 m_grid->ShowCol( col );
488
489 std::string key( m_dataModel->GetColFieldName( col ).ToUTF8() );
490
491 if( cfg->m_FieldEditorPanel.field_widths.count( key )
492 && ( cfg->m_FieldEditorPanel.field_widths.at( key ) > 0 ) )
493 {
494 m_grid->SetColSize( col, cfg->m_FieldEditorPanel.field_widths.at( key ) );
495 }
496 else
497 {
498 int textWidth = m_dataModel->GetDataWidth( col ) + COLUMN_MARGIN;
499 int maxWidth = defaultDlgSize.x / 3;
500
501 m_grid->SetColSize( col, std::clamp( textWidth, 100, maxWidth ) );
502 }
503 }
504 else
505 {
506 m_grid->HideCol( col );
507 }
508 }
509
510 m_dataModel->SetSorting( sortCol, sortAscending );
511 m_grid->SetSortingColumn( sortCol, sortAscending );
512}
513
514
516{
517 if( !wxDialog::TransferDataToWindow() )
518 return false;
519
520 LoadFieldNames(); // loads rows into m_viewControlsDataModel and columns into m_dataModel
521
522 // Load our BOM view presets
523 SetUserBomPresets( m_schSettings.m_BomPresets );
524
525 BOM_PRESET preset = m_schSettings.m_BomSettings;
526
527 if( m_job )
528 {
529 preset.name = m_job->m_bomPresetName;
530 preset.excludeDNP = m_job->m_excludeDNP;
531 preset.filterString = m_job->m_filterString;
532 preset.sortAsc = m_job->m_sortAsc;
533 preset.sortField = m_job->m_sortField;
534 preset.groupSymbols = m_job->m_groupSymbols;
535
536 preset.fieldsOrdered.clear();
537
538 size_t i = 0;
539
540 for( const wxString& fieldName : m_job->m_fieldsOrdered )
541 {
542 BOM_FIELD field;
543 field.name = fieldName;
544 field.show = !fieldName.StartsWith( wxT( "__" ), &field.name );
545 field.groupBy = alg::contains( m_job->m_fieldsGroupBy, field.name );
546
547 if( ( m_job->m_fieldsLabels.size() > i ) && !m_job->m_fieldsLabels[i].IsEmpty() )
548 field.label = m_job->m_fieldsLabels[i];
549 else if( IsGeneratedField( field.name ) )
550 field.label = GetGeneratedFieldDisplayName( field.name );
551 else
552 field.label = field.name;
553
554 preset.fieldsOrdered.emplace_back( field );
555 i++;
556 }
557 }
558
559 ApplyBomPreset( preset );
561
562 // Load BOM export format presets
563 SetUserBomFmtPresets( m_schSettings.m_BomFmtPresets );
564 BOM_FMT_PRESET fmtPreset = m_schSettings.m_BomFmtSettings;
565
566 if( m_job )
567 {
568 fmtPreset.name = m_job->m_bomFmtPresetName;
569 fmtPreset.fieldDelimiter = m_job->m_fieldDelimiter;
570 fmtPreset.keepLineBreaks = m_job->m_keepLineBreaks;
571 fmtPreset.keepTabs = m_job->m_keepTabs;
572 fmtPreset.refDelimiter = m_job->m_refDelimiter;
573 fmtPreset.refRangeDelimiter = m_job->m_refRangeDelimiter;
574 fmtPreset.stringDelimiter = m_job->m_stringDelimiter;
575 }
576
577 ApplyBomFmtPreset( fmtPreset );
579
580 TOOL_MANAGER* toolMgr = m_parent->GetToolManager();
581 SCH_SELECTION_TOOL* selectionTool = toolMgr->GetTool<SCH_SELECTION_TOOL>();
582 SCH_SELECTION& selection = selectionTool->GetSelection();
583 SCH_SYMBOL* symbol = nullptr;
584
585 m_dataModel->SetGroupingEnabled( m_groupSymbolsBox->GetValue() );
586
587 wxCommandEvent dummy;
588 OnScope( dummy );
589
590 if( selection.GetSize() == 1 )
591 {
592 EDA_ITEM* item = selection.Front();
593
594 if( item->Type() == SCH_SYMBOL_T )
595 symbol = (SCH_SYMBOL*) item;
596 else if( item->GetParent() && item->GetParent()->Type() == SCH_SYMBOL_T )
597 symbol = (SCH_SYMBOL*) item->GetParent();
598 }
599
600 if( symbol )
601 {
602 for( int row = 0; row < m_dataModel->GetNumberRows(); ++row )
603 {
604 std::vector<SCH_REFERENCE> references = m_dataModel->GetRowReferences( row );
605 bool found = false;
606
607 for( const SCH_REFERENCE& ref : references )
608 {
609 if( ref.GetSymbol() == symbol )
610 {
611 found = true;
612 break;
613 }
614 }
615
616 if( found )
617 {
618 // Find the value column and the reference column if they're shown
619 int valueCol = -1;
620 int refCol = -1;
621 int anyCol = -1;
622
623 for( int col = 0; col < m_dataModel->GetNumberCols(); col++ )
624 {
625 if( m_dataModel->ColIsValue( col ) )
626 valueCol = col;
627 else if( m_dataModel->ColIsReference( col ) )
628 refCol = col;
629 else if( anyCol == -1 && m_dataModel->GetShowColumn( col ) )
630 anyCol = col;
631 }
632
633 if( valueCol != -1 && m_dataModel->GetShowColumn( valueCol ) )
634 m_grid->GoToCell( row, valueCol );
635 else if( refCol != -1 && m_dataModel->GetShowColumn( refCol ) )
636 m_grid->GoToCell( row, refCol );
637 else if( anyCol != -1 )
638 m_grid->GoToCell( row, anyCol );
639
640 break;
641 }
642 }
643 }
644
645 // We don't want table range selection events to happen until we've loaded the data or we
646 // we'll clear our selection as the grid is built before the code above can get the
647 // user's current selection.
649
650 return true;
651}
652
653
655{
656 if( !m_grid->CommitPendingChanges() )
657 return false;
658
659 if( !wxDialog::TransferDataFromWindow() )
660 return false;
661
662 if( m_job )
663 {
664 // and exit, don't even dream of saving changes from the data model
665 return true;
666 }
667
668 SCH_COMMIT commit( m_parent );
669 SCH_SHEET_PATH currentSheet = m_parent->GetCurrentSheet();
670 wxString currentVariant = m_parent->Schematic().GetCurrentVariant();
671
672 m_dataModel->ApplyData( commit, m_schSettings.m_TemplateFieldNames, currentVariant );
673
674 if( !commit.Empty() )
675 {
676 commit.Push( wxS( "Symbol Fields Table Edit" ) ); // Push clears the commit buffer.
677 m_parent->OnModify();
678 }
679
680 // Reset the view to where we left the user
681 m_parent->SetCurrentSheet( currentSheet );
682 m_parent->SyncView();
683 m_parent->Refresh();
684
685 return true;
686}
687
688
689void DIALOG_SYMBOL_FIELDS_TABLE::AddField( const wxString& aFieldName, const wxString& aLabelValue,
690 bool show, bool groupBy, bool addedByUser )
691{
692 // Users can add fields with variable names that match the special names in the grid,
693 // e.g. ${QUANTITY} so make sure we don't add them twice
694 for( int row = 0; row < m_viewControlsDataModel->GetNumberRows(); row++ )
695 {
696 if( m_viewControlsDataModel->GetCanonicalFieldName( row ) == aFieldName )
697 return;
698 }
699
700 m_dataModel->AddColumn( aFieldName, aLabelValue, addedByUser, m_parent->Schematic().GetCurrentVariant() );
701
702 wxGridTableMessage msg( m_dataModel, wxGRIDTABLE_NOTIFY_COLS_APPENDED, 1 );
703 m_grid->ProcessTableMessage( msg );
704
705 m_viewControlsGrid->OnAddRow(
706 [&]() -> std::pair<int, int>
707 {
708 m_viewControlsDataModel->AppendRow( aFieldName, aLabelValue, show, groupBy );
709
710 return { m_viewControlsDataModel->GetNumberRows() - 1, -1 };
711 } );
712}
713
714
716{
717 auto addMandatoryField =
718 [&]( FIELD_T fieldId, bool show, bool groupBy )
719 {
720 m_mandatoryFieldListIndexes[fieldId] = m_viewControlsDataModel->GetNumberRows();
721
723 show, groupBy );
724 };
725
726 // Add mandatory fields first show groupBy
727 addMandatoryField( FIELD_T::REFERENCE, true, true );
728 addMandatoryField( FIELD_T::VALUE, true, true );
729 addMandatoryField( FIELD_T::FOOTPRINT, true, true );
730 addMandatoryField( FIELD_T::DATASHEET, true, false );
731 addMandatoryField( FIELD_T::DESCRIPTION, false, false );
732
733 // Generated fields present only in the fields table
736
737 // User fields next
738 std::set<wxString> userFieldNames;
739
740 for( int ii = 0; ii < (int) m_symbolsList.GetCount(); ++ii )
741 {
742 SCH_SYMBOL* symbol = m_symbolsList[ii].GetSymbol();
743
744 for( const SCH_FIELD& field : symbol->GetFields() )
745 {
746 if( !field.IsMandatory() && !field.IsPrivate() )
747 userFieldNames.insert( field.GetName() );
748 }
749 }
750
751 for( const wxString& fieldName : userFieldNames )
752 AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), true, false );
753
754 // Add any templateFieldNames which aren't already present in the userFieldNames
755 for( const TEMPLATE_FIELDNAME& tfn : m_schSettings.m_TemplateFieldNames.GetTemplateFieldNames() )
756 {
757 if( userFieldNames.count( tfn.m_Name ) == 0 )
758 AddField( tfn.m_Name, GetGeneratedFieldDisplayName( tfn.m_Name ), false, false );
759 }
760}
761
762
763void DIALOG_SYMBOL_FIELDS_TABLE::OnAddField( wxCommandEvent& event )
764{
765 wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Add Field" ) );
766
767 if( dlg.ShowModal() != wxID_OK )
768 return;
769
770 wxString fieldName = dlg.GetValue();
771
772 if( fieldName.IsEmpty() )
773 {
774 DisplayError( this, _( "Field must have a name." ) );
775 return;
776 }
777
778 for( int i = 0; i < m_dataModel->GetNumberCols(); ++i )
779 {
780 if( fieldName == m_dataModel->GetColFieldName( i ) )
781 {
782 DisplayError( this, wxString::Format( _( "Field name '%s' already in use." ), fieldName ) );
783 return;
784 }
785 }
786
787 AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), true, false, true );
788
789 SetupColumnProperties( m_dataModel->GetColsCount() - 1 );
790
792 OnModify();
793}
794
795
796void DIALOG_SYMBOL_FIELDS_TABLE::OnRemoveField( wxCommandEvent& event )
797{
798 m_viewControlsGrid->OnDeleteRows(
799 [&]( int row )
800 {
801 for( FIELD_T id : MANDATORY_FIELDS )
802 {
803 if( m_mandatoryFieldListIndexes[id] == row )
804 {
805 DisplayError( this, wxString::Format( _( "The first %d fields are mandatory." ),
806 (int) m_mandatoryFieldListIndexes.size() ) );
807 return false;
808 }
809 }
810
811 return IsOK( this, wxString::Format( _( "Are you sure you want to remove the field '%s'?" ),
812 m_viewControlsDataModel->GetValue( row, DISPLAY_NAME_COLUMN ) ) );
813 },
814 [&]( int row )
815 {
816 wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
817 int col = m_dataModel->GetFieldNameCol( fieldName );
818
819 if( col != -1 )
820 m_dataModel->RemoveColumn( col );
821
822 m_viewControlsDataModel->DeleteRow( row );
823
825 OnModify();
826 } );
827}
828
829
830void DIALOG_SYMBOL_FIELDS_TABLE::OnRenameField( wxCommandEvent& event )
831{
832 wxArrayInt selectedRows = m_viewControlsGrid->GetSelectedRows();
833
834 if( selectedRows.empty() && m_viewControlsGrid->GetGridCursorRow() >= 0 )
835 selectedRows.push_back( m_viewControlsGrid->GetGridCursorRow() );
836
837 if( selectedRows.empty() )
838 return;
839
840 int row = selectedRows[0];
841
842 for( FIELD_T id : MANDATORY_FIELDS )
843 {
844 if( m_mandatoryFieldListIndexes[id] == row )
845 {
846 DisplayError( this, wxString::Format( _( "The first %d fields are mandatory and names cannot be changed." ),
847 (int) m_mandatoryFieldListIndexes.size() ) );
848 return;
849 }
850 }
851
852 wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
853 wxString label = m_viewControlsDataModel->GetValue( row, LABEL_COLUMN );
854 bool labelIsAutogenerated = label.IsSameAs( GetGeneratedFieldDisplayName( fieldName ) );
855
856 int col = m_dataModel->GetFieldNameCol( fieldName );
857 wxCHECK_RET( col != -1, wxS( "Existing field name missing from data model" ) );
858
859 wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Rename Field" ), fieldName );
860
861 if( dlg.ShowModal() != wxID_OK )
862 return;
863
864 wxString newFieldName = dlg.GetValue();
865
866 // No change, no-op
867 if( newFieldName == fieldName )
868 return;
869
870 // New field name already exists
871 if( m_dataModel->GetFieldNameCol( newFieldName ) != -1 )
872 {
873 wxString confirm_msg = wxString::Format( _( "Field name %s already exists." ), newFieldName );
874 DisplayError( this, confirm_msg );
875 return;
876 }
877
878 m_dataModel->RenameColumn( col, newFieldName );
879 m_viewControlsDataModel->SetCanonicalFieldName( row, newFieldName );
880 m_viewControlsDataModel->SetValue( row, DISPLAY_NAME_COLUMN, newFieldName );
881
882 if( labelIsAutogenerated )
883 {
884 m_viewControlsDataModel->SetValue( row, LABEL_COLUMN, GetGeneratedFieldDisplayName( newFieldName ) );
885 wxGridEvent evt( m_viewControlsGrid->GetId(), wxEVT_GRID_CELL_CHANGED, m_viewControlsGrid, row, LABEL_COLUMN );
887 }
888
890 OnModify();
891}
892
893
894void DIALOG_SYMBOL_FIELDS_TABLE::OnFilterText( wxCommandEvent& aEvent )
895{
896 m_dataModel->SetFilter( m_filter->GetValue() );
897 m_dataModel->RebuildRows();
898 m_grid->ForceRefresh();
899
901}
902
903
905{
906#if defined( __WXOSX__ ) // Doesn't work properly on other ports
907 wxPoint pos = aEvent.GetPosition();
908 wxRect ctrlRect = m_filter->GetScreenRect();
909 int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
910
911 // TODO: restore cursor when mouse leaves the filter field (or is it a MSW bug?)
912 if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
913 SetCursor( wxCURSOR_ARROW );
914 else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
915 SetCursor( wxCURSOR_ARROW );
916 else
917 SetCursor( wxCURSOR_IBEAM );
918#endif
919}
920
921
923{
924 m_dataModel->SetPath( m_parent->GetCurrentSheet() );
925 m_dataModel->SetScope( aScope );
926 m_dataModel->RebuildRows();
927}
928
929
930void DIALOG_SYMBOL_FIELDS_TABLE::OnScope( wxCommandEvent& aEvent )
931{
932 switch( aEvent.GetSelection() )
933 {
934 case 0: setScope( SCOPE::SCOPE_ALL ); break;
935 case 1: setScope( SCOPE::SCOPE_SHEET ); break;
936 case 2: setScope( SCOPE::SCOPE_SHEET_RECURSIVE ); break;
937 }
938}
939
940
942{
943 m_dataModel->SetGroupingEnabled( m_groupSymbolsBox->GetValue() );
944 m_dataModel->RebuildRows();
945 m_grid->ForceRefresh();
946
948}
949
950
951void DIALOG_SYMBOL_FIELDS_TABLE::OnMenu( wxCommandEvent& event )
952{
953 EESCHEMA_SETTINGS::PANEL_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
954
955 // Build a pop menu:
956 wxMenu menu;
957
958 menu.Append( 4204, _( "Include 'DNP' Symbols" ),
959 _( "Show symbols marked 'DNP' in the table. This setting also controls whether or not 'DNP' "
960 "symbols are included on export." ),
961 wxITEM_CHECK );
962 menu.Check( 4204, !m_dataModel->GetExcludeDNP() );
963
964 menu.Append( 4205, _( "Include 'Exclude from BOM' Symbols" ),
965 _( "Show symbols marked 'Exclude from BOM' in the table. Symbols marked 'Exclude from BOM' "
966 "are never included on export." ),
967 wxITEM_CHECK );
968 menu.Check( 4205, m_dataModel->GetIncludeExcludedFromBOM() );
969
970 menu.AppendSeparator();
971
972 menu.Append( 4206, _( "Highlight on Cross-probe" ),
973 _( "Highlight corresponding item on canvas when it is selected in the table" ),
974 wxITEM_CHECK );
975 menu.Check( 4206, cfg.selection_mode == 0 );
976
977 menu.Append( 4207, _( "Select on Cross-probe" ),
978 _( "Select corresponding item on canvas when it is selected in the table" ),
979 wxITEM_CHECK );
980 menu.Check( 4207, cfg.selection_mode == 1 );
981
982 // menu_id is the selected submenu id from the popup menu or wxID_NONE
983 int menu_id = m_bMenu->GetPopupMenuSelectionFromUser( menu );
984
985 if( menu_id == 0 || menu_id == 4204 )
986 {
987 m_dataModel->SetExcludeDNP( !m_dataModel->GetExcludeDNP() );
988 m_dataModel->RebuildRows();
989 m_grid->ForceRefresh();
990
992 }
993 else if( menu_id == 1 || menu_id == 4205 )
994 {
995 m_dataModel->SetIncludeExcludedFromBOM( !m_dataModel->GetIncludeExcludedFromBOM() );
996 m_dataModel->RebuildRows();
997 m_grid->ForceRefresh();
998
1000 }
1001 else if( menu_id == 3 || menu_id == 4206 )
1002 {
1003 if( cfg.selection_mode != 0 )
1004 cfg.selection_mode = 0;
1005 else
1006 cfg.selection_mode = 2;
1007 }
1008 else if( menu_id == 4 || menu_id == 4207 )
1009 {
1010 if( cfg.selection_mode != 1 )
1011 cfg.selection_mode = 1;
1012 else
1013 cfg.selection_mode = 2;
1014 }
1015}
1016
1017
1018void DIALOG_SYMBOL_FIELDS_TABLE::OnColSort( wxGridEvent& aEvent )
1019{
1020 int sortCol = aEvent.GetCol();
1021 std::string key( m_dataModel->GetColFieldName( sortCol ).ToUTF8() );
1022 bool ascending;
1023
1024 // Don't sort by item number, it is generated by the sort
1025 if( m_dataModel->ColIsItemNumber( sortCol ) )
1026 {
1027 aEvent.Veto();
1028 return;
1029 }
1030
1031 // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the event, and
1032 // if we ask it will give us pre-event info.
1033 if( m_grid->IsSortingBy( sortCol ) )
1034 {
1035 // same column; invert ascending
1036 ascending = !m_grid->IsSortOrderAscending();
1037 }
1038 else
1039 {
1040 // different column; start with ascending
1041 ascending = true;
1042 }
1043
1044 m_dataModel->SetSorting( sortCol, ascending );
1045 m_dataModel->RebuildRows();
1046 m_grid->ForceRefresh();
1047
1049}
1050
1051
1052void DIALOG_SYMBOL_FIELDS_TABLE::OnColMove( wxGridEvent& aEvent )
1053{
1054 int origPos = aEvent.GetCol();
1055
1056 // Save column widths since the setup function uses the saved config values
1057 EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
1058
1059 for( int i = 0; i < m_grid->GetNumberCols(); i++ )
1060 {
1061 if( m_grid->IsColShown( i ) )
1062 {
1063 std::string fieldName( m_dataModel->GetColFieldName( i ).ToUTF8() );
1064 cfg->m_FieldEditorPanel.field_widths[fieldName] = m_grid->GetColSize( i );
1065 }
1066 }
1067
1068 CallAfter(
1069 [origPos, this]()
1070 {
1071 int newPos = m_grid->GetColPos( origPos );
1072
1073#ifdef __WXMAC__
1074 if( newPos < origPos )
1075 newPos += 1;
1076#endif
1077
1078 m_dataModel->MoveColumn( origPos, newPos );
1079
1080 // "Unmove" the column since we've moved the column internally
1081 m_grid->ResetColPos();
1082
1083 // We need to reset all the column attr's to the correct column order
1085
1086 m_grid->ForceRefresh();
1087 } );
1088
1090}
1091
1092
1094{
1095 if( aShow )
1096 m_grid->ShowCol( aCol );
1097 else
1098 m_grid->HideCol( aCol );
1099
1100 m_dataModel->SetShowColumn( aCol, aShow );
1101
1103
1104 if( m_nbPages->GetSelection() == 1 )
1106 else
1107 m_grid->ForceRefresh();
1108
1109 OnModify();
1110}
1111
1112
1114{
1115 int row = aEvent.GetRow();
1116
1117 wxCHECK( row < m_viewControlsGrid->GetNumberRows(), /* void */ );
1118
1119 switch( aEvent.GetCol() )
1120 {
1121 case LABEL_COLUMN:
1122 {
1123 wxString label = m_viewControlsDataModel->GetValue( row, LABEL_COLUMN );
1124 wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
1125 int dataCol = m_dataModel->GetFieldNameCol( fieldName );
1126
1127 if( dataCol != -1 )
1128 {
1129 m_dataModel->SetColLabelValue( dataCol, label );
1130 m_grid->SetColLabelValue( dataCol, label );
1131
1132 if( m_nbPages->GetSelection() == 1 )
1134 else
1135 m_grid->ForceRefresh();
1136
1138 OnModify();
1139 }
1140
1141 break;
1142 }
1143
1144 case SHOW_FIELD_COLUMN:
1145 {
1146 wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
1147 bool value = m_viewControlsDataModel->GetValueAsBool( row, SHOW_FIELD_COLUMN );
1148 int dataCol = m_dataModel->GetFieldNameCol( fieldName );
1149
1150 if( dataCol != -1 )
1151 ShowHideColumn( dataCol, value );
1152
1153 break;
1154 }
1155
1156 case GROUP_BY_COLUMN:
1157 {
1158 wxString fieldName = m_viewControlsDataModel->GetCanonicalFieldName( row );
1159 bool value = m_viewControlsDataModel->GetValueAsBool( row, GROUP_BY_COLUMN );
1160 int dataCol = m_dataModel->GetFieldNameCol( fieldName );
1161
1162 if( m_dataModel->ColIsQuantity( dataCol ) && value )
1163 {
1164 DisplayError( this, _( "The Quantity column cannot be grouped by." ) );
1165
1166 value = false;
1167 m_viewControlsDataModel->SetValueAsBool( row, GROUP_BY_COLUMN, value );
1168 break;
1169 }
1170
1171 if( m_dataModel->ColIsItemNumber( dataCol ) && value )
1172 {
1173 DisplayError( this, _( "The Item Number column cannot be grouped by." ) );
1174
1175 value = false;
1176 m_viewControlsDataModel->SetValueAsBool( row, GROUP_BY_COLUMN, value );
1177 break;
1178 }
1179
1180 m_dataModel->SetGroupColumn( dataCol, value );
1181 m_dataModel->RebuildRows();
1182
1183 if( m_nbPages->GetSelection() == 1 )
1185 else
1186 m_grid->ForceRefresh();
1187
1189 OnModify();
1190 break;
1191 }
1192
1193 default:
1194 break;
1195 }
1196}
1197
1198
1200{
1201 m_grid->ForceRefresh();
1202}
1203
1204
1205void DIALOG_SYMBOL_FIELDS_TABLE::OnTableColSize( wxGridSizeEvent& aEvent )
1206{
1207 aEvent.Skip();
1208
1209 m_grid->ForceRefresh();
1210}
1211
1212
1214{
1215 m_dataModel->RebuildRows();
1216 m_grid->ForceRefresh();
1217}
1218
1219
1221{
1222 if( m_dataModel->IsExpanderColumn( event.GetCol() ) )
1223 {
1224 m_grid->ClearSelection();
1225
1226 m_dataModel->ExpandCollapseRow( event.GetRow() );
1227 m_grid->SetGridCursor( event.GetRow(), event.GetCol() );
1228 }
1229 else
1230 {
1231 event.Skip();
1232 }
1233}
1234
1235
1237{
1238 aEvent.Skip();
1239
1240 wxPoint pos = aEvent.GetPosition();
1241 int ux, uy;
1242 m_grid->CalcUnscrolledPosition( pos.x, pos.y, &ux, &uy );
1243 int row = m_grid->YToRow( uy );
1244 int col = m_grid->XToCol( ux );
1245
1246
1247 if( row == wxNOT_FOUND || col == wxNOT_FOUND )
1248 {
1249 m_grid->GetGridWindow()->UnsetToolTip();
1250 return;
1251 }
1252
1253 wxString rawValue = m_dataModel->GetValue( row, col );
1254
1255 if( rawValue.Contains( wxT( "${" ) ) )
1256 {
1257 m_grid->GetGridWindow()->SetToolTip( rawValue );
1258 }
1259 else
1260 {
1261 m_grid->GetGridWindow()->UnsetToolTip();
1262 }
1263}
1264
1265
1266void DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected( wxGridRangeSelectEvent& aEvent )
1267{
1268 EESCHEMA_SETTINGS::PANEL_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
1269
1270 // Cross-probing should only work in Edit page
1271 if( m_nbPages->GetSelection() != 0 )
1272 return;
1273
1274 // Multi-select can grab the rows that are expanded child refs, and also the row
1275 // containing the list of all child refs. Make sure we add refs/symbols uniquely
1276 std::set<SCH_REFERENCE> refs;
1277 std::set<SCH_ITEM*> symbols;
1278
1279 // This handler handles selecting and deselecting
1280 if( aEvent.Selecting() )
1281 {
1282 for( int i = aEvent.GetTopRow(); i <= aEvent.GetBottomRow(); i++ )
1283 {
1284 for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( i ) )
1285 refs.insert( ref );
1286 }
1287
1288 for( const SCH_REFERENCE& ref : refs )
1289 symbols.insert( ref.GetSymbol() );
1290 }
1291
1292 if( cfg.selection_mode == 0 )
1293 {
1294 SCH_EDITOR_CONTROL* editor = m_parent->GetToolManager()->GetTool<SCH_EDITOR_CONTROL>();
1295
1296 if( refs.size() > 0 )
1297 {
1298 // Use of full path based on UUID allows select of not yet annotated or duplicated
1299 // symbols
1300 wxString symbol_path = refs.begin()->GetFullPath();
1301
1302 // Focus only handles one item at this time
1303 editor->FindSymbolAndItem( &symbol_path, nullptr, true, HIGHLIGHT_SYMBOL, wxEmptyString );
1304 }
1305 else
1306 {
1307 m_parent->ClearFocus();
1308 }
1309 }
1310 else if( cfg.selection_mode == 1 )
1311 {
1312 SCH_SELECTION_TOOL* selTool = m_parent->GetToolManager()->GetTool<SCH_SELECTION_TOOL>();
1313 std::vector<SCH_ITEM*> items( symbols.begin(), symbols.end() );
1314
1315 if( refs.size() > 0 )
1316 selTool->SyncSelection( refs.begin()->GetSheetPath(), nullptr, items );
1317 else
1318 selTool->ClearSelection();
1319 }
1320}
1321
1322
1324{
1325 const wxString& showColLabel = m_viewControlsGrid->GetColLabelValue( SHOW_FIELD_COLUMN );
1326 const wxString& groupByColLabel = m_viewControlsGrid->GetColLabelValue( GROUP_BY_COLUMN );
1327 int showColWidth = KIUI::GetTextSize( showColLabel, m_viewControlsGrid ).x + COLUMN_MARGIN;
1328 int groupByColWidth = KIUI::GetTextSize( groupByColLabel, m_viewControlsGrid ).x + COLUMN_MARGIN;
1329 int remainingWidth = m_viewControlsGrid->GetSize().GetX() - showColWidth - groupByColWidth;
1330
1331 m_viewControlsGrid->SetColSize( showColWidth, SHOW_FIELD_COLUMN );
1332 m_viewControlsGrid->SetColSize( groupByColWidth, GROUP_BY_COLUMN );
1333
1334 if( m_viewControlsGrid->IsColShown( DISPLAY_NAME_COLUMN ) && m_viewControlsGrid->IsColShown( LABEL_COLUMN ) )
1335 {
1336 m_viewControlsGrid->SetColSize( DISPLAY_NAME_COLUMN, std::max( remainingWidth / 2, 60 ) );
1337 m_viewControlsGrid->SetColSize( LABEL_COLUMN, std::max( remainingWidth - ( remainingWidth / 2 ), 60 ) );
1338 }
1339 else if( m_viewControlsGrid->IsColShown( DISPLAY_NAME_COLUMN ) )
1340 {
1341 m_viewControlsGrid->SetColSize( DISPLAY_NAME_COLUMN, std::max( remainingWidth, 60 ) );
1342 }
1343 else if( m_viewControlsGrid->IsColShown( LABEL_COLUMN ) )
1344 {
1345 m_viewControlsGrid->SetColSize( LABEL_COLUMN, std::max( remainingWidth, 60 ) );
1346 }
1347
1348 event.Skip();
1349}
1350
1351
1353{
1355 {
1356 m_parent->SaveProject();
1357 ClearModify();
1358 }
1359}
1360
1361
1362void DIALOG_SYMBOL_FIELDS_TABLE::OnPageChanged( wxNotebookEvent& event )
1363{
1364 if( m_dataModel->GetColsCount() )
1366}
1367
1368
1370{
1373}
1374
1375
1377{
1378 bool saveIncludeExcudedFromBOM = m_dataModel->GetIncludeExcludedFromBOM();
1379
1380 m_dataModel->SetIncludeExcludedFromBOM( false );
1381 m_dataModel->RebuildRows();
1382
1383 m_textOutput->SetValue( m_dataModel->Export( GetCurrentBomFmtSettings() ) );
1384
1385 if( saveIncludeExcudedFromBOM )
1386 {
1387 m_dataModel->SetIncludeExcludedFromBOM( true );
1388 m_dataModel->RebuildRows();
1389 }
1390}
1391
1392
1394{
1395 BOM_FMT_PRESET current;
1396
1397 current.name = m_cbBomFmtPresets->GetStringSelection();
1398 current.fieldDelimiter = m_textFieldDelimiter->GetValue();
1399 current.stringDelimiter = m_textStringDelimiter->GetValue();
1400 current.refDelimiter = m_textRefDelimiter->GetValue();
1401 current.refRangeDelimiter = m_textRefRangeDelimiter->GetValue();
1402 current.keepTabs = m_checkKeepTabs->GetValue();
1403 current.keepLineBreaks = m_checkKeepLineBreaks->GetValue();
1404
1405 return current;
1406}
1407
1408
1410{
1411 m_nbPages->SetSelection( 0 );
1412}
1413
1414
1416{
1417 m_nbPages->SetSelection( 1 );
1418}
1419
1420
1422{
1423 // Build the absolute path of current output directory to preselect it in the file browser.
1424 wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() );
1425 path = Prj().AbsolutePath( path );
1426
1427
1428 // Calculate the export filename
1429 wxFileName fn( Prj().AbsolutePath( m_parent->Schematic().GetFileName() ) );
1430 fn.SetExt( FILEEXT::CsvFileExtension );
1431
1432 wxFileDialog saveDlg( this, _( "Bill of Materials Output File" ), path, fn.GetFullName(),
1433 FILEEXT::CsvFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
1434
1436
1437 if( saveDlg.ShowModal() == wxID_CANCEL )
1438 return;
1439
1440
1441 wxFileName file = wxFileName( saveDlg.GetPath() );
1442 wxString defaultPath = fn.GetPathWithSep();
1443
1444 if( IsOK( this, wxString::Format( _( "Do you want to use a path relative to\n'%s'?" ), defaultPath ) ) )
1445 {
1446 if( !file.MakeRelativeTo( defaultPath ) )
1447 {
1448 DisplayErrorMessage( this, _( "Cannot make path relative (target volume different from schematic "
1449 "file volume)!" ) );
1450 }
1451 }
1452
1453 m_outputFileName->SetValue( file.GetFullPath() );
1454}
1455
1456
1458{
1459 EESCHEMA_SETTINGS::PANEL_SYMBOL_FIELDS_TABLE& cfg = m_parent->eeconfig()->m_FieldEditorPanel;
1460
1461 if( cfg.sidebar_collapsed )
1462 {
1463 cfg.sidebar_collapsed = false;
1464 m_splitterMainWindow->SplitVertically( m_leftPanel, m_rightPanel, cfg.sash_pos );
1465 }
1466 else
1467 {
1468 cfg.sash_pos = m_splitterMainWindow->GetSashPosition();
1469
1470 cfg.sidebar_collapsed = true;
1472 }
1473
1475}
1476
1477
1478void DIALOG_SYMBOL_FIELDS_TABLE::OnExport( wxCommandEvent& aEvent )
1479{
1480 if( m_dataModel->IsEdited() )
1481 {
1482 if( OKOrCancelDialog( nullptr, _( "Unsaved data" ),
1483 _( "Changes have not yet been saved. Export unsaved data?" ), "",
1484 _( "OK" ), _( "Cancel" ) )
1485 == wxID_CANCEL )
1486 {
1487 return;
1488 }
1489 }
1490
1491 // Create output directory if it does not exist (also transform it in absolute form).
1492 // Bail if it fails.
1493
1494 std::function<bool( wxString* )> textResolver =
1495 [&]( wxString* token ) -> bool
1496 {
1497 SCHEMATIC& schematic = m_parent->Schematic();
1498
1499 // Handles m_board->GetTitleBlock() *and* m_board->GetProject()
1500 return schematic.ResolveTextVar( &schematic.CurrentSheet(), token, 0 );
1501 };
1502
1503 wxString path = m_outputFileName->GetValue();
1504
1505 if( path.IsEmpty() )
1506 {
1507 DisplayError( this, _( "No output file specified in Export tab." ) );
1508 return;
1509 }
1510
1511 path = ExpandTextVars( path, &textResolver );
1513
1514 wxFileName outputFile = wxFileName::FileName( path );
1515 wxString msg;
1516
1517 if( !EnsureFileDirectoryExists( &outputFile, Prj().AbsolutePath( m_parent->Schematic().GetFileName() ),
1519 {
1520 msg.Printf( _( "Could not open/create path '%s'." ), outputFile.GetPath() );
1521 DisplayError( this, msg );
1522 return;
1523 }
1524
1525 wxFFile out( outputFile.GetFullPath(), "wb" );
1526
1527 if( !out.IsOpened() )
1528 {
1529 msg.Printf( _( "Could not create BOM output '%s'." ), outputFile.GetFullPath() );
1530 DisplayError( this, msg );
1531 return;
1532 }
1533
1535
1536 if( !out.Write( m_textOutput->GetValue() ) )
1537 {
1538 msg.Printf( _( "Could not write BOM output '%s'." ), outputFile.GetFullPath() );
1539 DisplayError( this, msg );
1540 return;
1541 }
1542
1543 // close the file before we tell the user it's done with the info modal :workflow meme:
1544 out.Close();
1545 msg.Printf( _( "Wrote BOM output to '%s'" ), outputFile.GetFullPath() );
1546 DisplayInfoMessage( this, msg );
1547}
1548
1549
1550void DIALOG_SYMBOL_FIELDS_TABLE::OnCancel( wxCommandEvent& aEvent )
1551{
1552 if( m_job )
1553 EndModal( wxID_CANCEL );
1554 else
1555 Close();
1556}
1557
1558
1559void DIALOG_SYMBOL_FIELDS_TABLE::OnOk( wxCommandEvent& aEvent )
1560{
1562
1563 if( m_job )
1564 {
1565 m_job->SetConfiguredOutputPath( m_outputFileName->GetValue() );
1566
1568 m_job->m_bomFmtPresetName = m_currentBomFmtPreset->name;
1569 else
1570 m_job->m_bomFmtPresetName = wxEmptyString;
1571
1572 if( m_currentBomPreset )
1573 m_job->m_bomPresetName = m_currentBomPreset->name;
1574 else
1575 m_job->m_bomPresetName = wxEmptyString;
1576
1578 m_job->m_fieldDelimiter = fmtSettings.fieldDelimiter;
1579 m_job->m_stringDelimiter = fmtSettings.stringDelimiter;
1580 m_job->m_refDelimiter = fmtSettings.refDelimiter;
1581 m_job->m_refRangeDelimiter = fmtSettings.refRangeDelimiter;
1582 m_job->m_keepTabs = fmtSettings.keepTabs;
1583 m_job->m_keepLineBreaks = fmtSettings.keepLineBreaks;
1584
1585 BOM_PRESET presetFields = m_dataModel->GetBomSettings();
1586 m_job->m_sortAsc = presetFields.sortAsc;
1587 m_job->m_excludeDNP = presetFields.excludeDNP;
1588 m_job->m_filterString = presetFields.filterString;
1589 m_job->m_sortField = presetFields.sortField;
1590 m_job->m_groupSymbols = presetFields.groupSymbols;
1591
1592 m_job->m_fieldsOrdered.clear();
1593 m_job->m_fieldsLabels.clear();
1594 m_job->m_fieldsGroupBy.clear();
1595
1596 for( const BOM_FIELD& modelField : m_dataModel->GetFieldsOrdered() )
1597 {
1598 if( modelField.show )
1599 m_job->m_fieldsOrdered.emplace_back( modelField.name );
1600 else
1601 m_job->m_fieldsOrdered.emplace_back( wxT( "__" ) + modelField.name );
1602
1603 m_job->m_fieldsLabels.emplace_back( modelField.label );
1604
1605 if( modelField.groupBy )
1606 m_job->m_fieldsGroupBy.emplace_back( modelField.name );
1607 }
1608
1609 wxString selectedVariant = getSelectedVariant();
1610
1611 if( !selectedVariant.IsEmpty() )
1612 m_job->m_variantNames.push_back( selectedVariant );
1613
1614 EndModal( wxID_OK );
1615 }
1616 else
1617 {
1618 Close();
1619 }
1620}
1621
1622
1623void DIALOG_SYMBOL_FIELDS_TABLE::OnClose( wxCloseEvent& aEvent )
1624{
1625 if( m_job )
1626 {
1627 aEvent.Skip();
1628 return;
1629 }
1630
1631 m_grid->CommitPendingChanges( true );
1632
1633 if( m_dataModel->IsEdited() && aEvent.CanVeto() )
1634 {
1635 if( !HandleUnsavedChanges( this, _( "Save changes?" ),
1636 [&]() -> bool
1637 {
1638 return TransferDataFromWindow();
1639 } ) )
1640 {
1641 aEvent.Veto();
1642 return;
1643 }
1644 }
1645
1646 // Stop listening to schematic events
1647 m_parent->Schematic().RemoveListener( this );
1648 m_parent->ClearFocus();
1649
1650 wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_CLOSE_DIALOG_SYMBOL_FIELDS_TABLE, wxID_ANY );
1651
1652 if( wxWindow* parent = GetParent() )
1653 wxQueueEvent( parent, evt );
1654}
1655
1656
1658{
1659 std::vector<BOM_PRESET> ret;
1660
1661 for( const std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
1662 {
1663 if( !pair.second.readOnly )
1664 ret.emplace_back( pair.second );
1665 }
1666
1667 return ret;
1668}
1669
1670
1671void DIALOG_SYMBOL_FIELDS_TABLE::SetUserBomPresets( std::vector<BOM_PRESET>& aPresetList )
1672{
1673 // Reset to defaults
1675
1676 for( const BOM_PRESET& preset : aPresetList )
1677 {
1678 if( m_bomPresets.count( preset.name ) )
1679 continue;
1680
1681 m_bomPresets[preset.name] = preset;
1682
1683 m_bomPresetMRU.Add( preset.name );
1684 }
1685
1687}
1688
1689
1690void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomPreset( const wxString& aPresetName )
1691{
1692 updateBomPresetSelection( aPresetName );
1693
1694 wxCommandEvent dummy;
1696}
1697
1698
1700{
1701 if( m_bomPresets.count( aPreset.name ) )
1703 else
1704 m_currentBomPreset = nullptr;
1705
1706 if( m_currentBomPreset && !m_currentBomPreset->readOnly )
1708 else
1709 m_lastSelectedBomPreset = nullptr;
1710
1711 updateBomPresetSelection( aPreset.name );
1712 doApplyBomPreset( aPreset );
1713}
1714
1715
1717{
1718 m_bomPresets.clear();
1719 m_bomPresetMRU.clear();
1720
1721 // Load the read-only defaults
1722 for( const BOM_PRESET& preset : BOM_PRESET::BuiltInPresets() )
1723 {
1724 m_bomPresets[preset.name] = preset;
1725 m_bomPresets[preset.name].readOnly = true;
1726
1727 m_bomPresetMRU.Add( preset.name );
1728 }
1729}
1730
1731
1733{
1734 m_cbBomPresets->Clear();
1735
1736 int idx = 0;
1737 int default_idx = 0;
1738
1739 for( const auto& [presetName, preset] : m_bomPresets )
1740 {
1741 m_cbBomPresets->Append( wxGetTranslation( presetName ), (void*) &preset );
1742
1743 if( presetName == BOM_PRESET::DefaultEditing().name )
1744 default_idx = idx;
1745
1746 idx++;
1747 }
1748
1749 m_cbBomPresets->Append( wxT( "---" ) );
1750 m_cbBomPresets->Append( _( "Save preset..." ) );
1751 m_cbBomPresets->Append( _( "Delete preset..." ) );
1752
1753 // At least the built-in presets should always be present
1754 wxASSERT( !m_bomPresets.empty() );
1755
1756 m_cbBomPresets->SetSelection( default_idx );
1757 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( default_idx ) );
1758}
1759
1760
1762{
1763 BOM_PRESET current = m_dataModel->GetBomSettings();
1764
1765 auto it = std::find_if( m_bomPresets.begin(), m_bomPresets.end(),
1766 [&]( const std::pair<const wxString, BOM_PRESET>& aPair )
1767 {
1768 const BOM_PRESET& preset = aPair.second;
1769
1770 // Check the simple settings first
1771 if( !( preset.sortAsc == current.sortAsc
1772 && preset.filterString == current.filterString
1773 && preset.groupSymbols == current.groupSymbols
1774 && preset.excludeDNP == current.excludeDNP
1775 && preset.includeExcludedFromBOM == current.includeExcludedFromBOM ) )
1776 {
1777 return false;
1778 }
1779
1780 // We should compare preset.name and current.name. Unfortunately current.name is
1781 // empty because m_dataModel->GetBomSettings() does not store the .name member.
1782 // So use sortField member as a (not very efficient) auxiliary filter.
1783 // As a further complication, sortField can be translated in m_bomPresets list, so
1784 // current.sortField needs to be translated.
1785 // Probably this not efficient and error prone test should be removed (JPC).
1786 if( preset.sortField != wxGetTranslation( current.sortField ) )
1787 return false;
1788
1789 // Only compare shown or grouped fields
1790 std::vector<BOM_FIELD> A, B;
1791
1792 for( const BOM_FIELD& field : preset.fieldsOrdered )
1793 {
1794 if( field.show || field.groupBy )
1795 A.emplace_back( field );
1796 }
1797
1798 for( const BOM_FIELD& field : current.fieldsOrdered )
1799 {
1800 if( field.show || field.groupBy )
1801 B.emplace_back( field );
1802 }
1803
1804 return A == B;
1805 } );
1806
1807 if( it != m_bomPresets.end() )
1808 {
1809 // Select the right m_cbBomPresets item.
1810 // but these items are translated if they are predefined items.
1811 bool do_translate = it->second.readOnly;
1812 wxString text = do_translate ? wxGetTranslation( it->first ) : it->first;
1813 m_cbBomPresets->SetStringSelection( text );
1814 }
1815 else
1816 {
1817 m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 ); // separator
1818 }
1819
1820 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( m_cbBomPresets->GetSelection() ) );
1821}
1822
1823
1825{
1826 // Look at m_userBomPresets to know if aName is a read only preset, or a user preset.
1827 // Read-only presets have translated names in UI, so we have to use a translated name
1828 // in UI selection. But for a user preset name we search for the untranslated aName.
1829 wxString ui_label = aName;
1830
1831 for( const auto& [presetName, preset] : m_bomPresets )
1832 {
1833 if( presetName == aName )
1834 {
1835 if( preset.readOnly == true )
1836 ui_label = wxGetTranslation( aName );
1837
1838 break;
1839 }
1840 }
1841
1842 int idx = m_cbBomPresets->FindString( ui_label );
1843
1844 if( idx >= 0 && m_cbBomPresets->GetSelection() != idx )
1845 {
1846 m_cbBomPresets->SetSelection( idx );
1847 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( idx ) );
1848 }
1849 else if( idx < 0 )
1850 {
1851 m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 ); // separator
1852 }
1853}
1854
1855
1857{
1858 int count = m_cbBomPresets->GetCount();
1859 int index = m_cbBomPresets->GetSelection();
1860
1861 auto resetSelection =
1862 [&]()
1863 {
1864 if( m_currentBomPreset )
1865 m_cbBomPresets->SetStringSelection( m_currentBomPreset->name );
1866 else
1867 m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 );
1868 };
1869
1870 if( index == count - 3 )
1871 {
1872 // Separator: reject the selection
1873 resetSelection();
1874 return;
1875 }
1876 else if( index == count - 2 )
1877 {
1878 // Save current state to new preset
1879 wxString name;
1880
1883
1884 wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
1885
1886 if( dlg.ShowModal() != wxID_OK )
1887 {
1888 resetSelection();
1889 return;
1890 }
1891
1892 name = dlg.GetValue();
1893 bool exists = m_bomPresets.count( name );
1894
1895 if( !exists )
1896 {
1897 m_bomPresets[name] = m_dataModel->GetBomSettings();
1898 m_bomPresets[name].readOnly = false;
1899 m_bomPresets[name].name = name;
1900 }
1901
1902 BOM_PRESET* preset = &m_bomPresets[name];
1903
1904 if( !exists )
1905 {
1906 index = m_cbBomPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
1907 }
1908 else if( preset->readOnly )
1909 {
1910 wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
1911 _( "Error" ), wxOK | wxICON_ERROR, this );
1912 resetSelection();
1913 return;
1914 }
1915 else
1916 {
1917 // Ask the user if they want to overwrite the existing preset
1918 if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
1919 {
1920 resetSelection();
1921 return;
1922 }
1923
1924 *preset = m_dataModel->GetBomSettings();
1925 preset->name = name;
1926
1927 index = m_cbBomPresets->FindString( name );
1928
1929 if( m_bomPresetMRU.Index( name ) != wxNOT_FOUND )
1930 m_bomPresetMRU.Remove( name );
1931 }
1932
1933 m_currentBomPreset = preset;
1934 m_cbBomPresets->SetSelection( index );
1935 m_bomPresetMRU.Insert( name, 0 );
1936
1937 return;
1938 }
1939 else if( index == count - 1 )
1940 {
1941 // Delete a preset
1942 wxArrayString headers;
1943 std::vector<wxArrayString> items;
1944
1945 headers.Add( _( "Presets" ) );
1946
1947 for( const auto& [name, preset] : m_bomPresets )
1948 {
1949 if( !preset.readOnly )
1950 {
1951 wxArrayString item;
1952 item.Add( name );
1953 items.emplace_back( item );
1954 }
1955 }
1956
1957 EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
1958 dlg.SetListLabel( _( "Select preset:" ) );
1959
1960 if( dlg.ShowModal() == wxID_OK )
1961 {
1962 wxString presetName = dlg.GetTextSelection();
1963 int idx = m_cbBomPresets->FindString( presetName );
1964
1965 if( idx != wxNOT_FOUND )
1966 {
1967 m_bomPresets.erase( presetName );
1968
1969 m_cbBomPresets->Delete( idx );
1970 m_currentBomPreset = nullptr;
1971 }
1972
1973 if( m_bomPresetMRU.Index( presetName ) != wxNOT_FOUND )
1974 m_bomPresetMRU.Remove( presetName );
1975 }
1976
1977 resetSelection();
1978 return;
1979 }
1980
1981 BOM_PRESET* preset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( index ) );
1982 m_currentBomPreset = preset;
1983
1984 m_lastSelectedBomPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
1985
1986 if( preset )
1987 {
1988 doApplyBomPreset( *preset );
1990 m_currentBomPreset = preset;
1991
1992 if( !m_currentBomPreset->name.IsEmpty() )
1993 {
1994 if( m_bomPresetMRU.Index( preset->name ) != wxNOT_FOUND )
1995 m_bomPresetMRU.Remove( preset->name );
1996
1997 m_bomPresetMRU.Insert( preset->name, 0 );
1998 }
1999 }
2000}
2001
2002
2004{
2005 // Disable rebuilds while we're applying the preset otherwise we'll be
2006 // rebuilding the model constantly while firing off wx events
2007 m_dataModel->DisableRebuilds();
2008
2009 // Basically, we apply the BOM preset to the data model and then
2010 // update our UI to reflect resulting the data model state, not the preset.
2011 m_dataModel->ApplyBomPreset( aPreset, m_parent->Schematic().GetCurrentVariant() );
2012
2013 // BOM Presets can add, but not remove, columns, so make sure the view controls
2014 // grid has all of them before starting
2015 for( int i = 0; i < m_dataModel->GetColsCount(); i++ )
2016 {
2017 const wxString& fieldName( m_dataModel->GetColFieldName( i ) );
2018 bool found = false;
2019
2020 for( int j = 0; j < m_viewControlsDataModel->GetNumberRows(); j++ )
2021 {
2022 if( m_viewControlsDataModel->GetCanonicalFieldName( j ) == fieldName )
2023 {
2024 found = true;
2025 break;
2026 }
2027 }
2028
2029 // Properties like label, etc. will be added in the next loop
2030 if( !found )
2031 AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), false, false );
2032 }
2033
2034 // Sync all fields
2035 for( int i = 0; i < m_viewControlsDataModel->GetNumberRows(); i++ )
2036 {
2037 const wxString& fieldName( m_viewControlsDataModel->GetCanonicalFieldName( i ) );
2038 int col = m_dataModel->GetFieldNameCol( fieldName );
2039
2040 if( col == -1 )
2041 {
2042 wxASSERT_MSG( true, "Fields control has a field not found in the data model." );
2043 continue;
2044 }
2045
2046 EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
2047 std::string fieldNameStr( fieldName.ToUTF8() );
2048
2049 // Set column labels
2050 const wxString& label = m_dataModel->GetColLabelValue( col );
2051 m_viewControlsDataModel->SetValue( i, LABEL_COLUMN, label );
2052 m_grid->SetColLabelValue( col, label );
2053
2054 if( cfg->m_FieldEditorPanel.field_widths.count( fieldNameStr ) )
2055 m_grid->SetColSize( col, cfg->m_FieldEditorPanel.field_widths.at( fieldNameStr ) );
2056
2057 // Set shown columns
2058 bool show = m_dataModel->GetShowColumn( col );
2059 m_viewControlsDataModel->SetValueAsBool( i, SHOW_FIELD_COLUMN, show );
2060
2061 if( show )
2062 m_grid->ShowCol( col );
2063 else
2064 m_grid->HideCol( col );
2065
2066 // Set grouped columns
2067 bool groupBy = m_dataModel->GetGroupColumn( col );
2068 m_viewControlsDataModel->SetValueAsBool( i, GROUP_BY_COLUMN, groupBy );
2069 }
2070
2071 m_grid->SetSortingColumn( m_dataModel->GetSortCol(), m_dataModel->GetSortAsc() );
2072 m_groupSymbolsBox->SetValue( m_dataModel->GetGroupingEnabled() );
2073 m_filter->ChangeValue( m_dataModel->GetFilter() );
2074
2076
2077 // This will rebuild all rows and columns in the model such that the order
2078 // and labels are right, then we refresh the shown grid data to match
2079 m_dataModel->EnableRebuilds();
2080 m_dataModel->RebuildRows();
2081
2082 if( m_nbPages->GetSelection() == 1 )
2084 else
2085 m_grid->ForceRefresh();
2086}
2087
2088
2089std::vector<BOM_FMT_PRESET> DIALOG_SYMBOL_FIELDS_TABLE::GetUserBomFmtPresets() const
2090{
2091 std::vector<BOM_FMT_PRESET> ret;
2092
2093 for( const auto& [name, preset] : m_bomFmtPresets )
2094 {
2095 if( !preset.readOnly )
2096 ret.emplace_back( preset );
2097 }
2098
2099 return ret;
2100}
2101
2102
2103void DIALOG_SYMBOL_FIELDS_TABLE::SetUserBomFmtPresets( std::vector<BOM_FMT_PRESET>& aPresetList )
2104{
2105 // Reset to defaults
2107
2108 for( const BOM_FMT_PRESET& preset : aPresetList )
2109 {
2110 if( m_bomFmtPresets.count( preset.name ) )
2111 continue;
2112
2113 m_bomFmtPresets[preset.name] = preset;
2114
2115 m_bomFmtPresetMRU.Add( preset.name );
2116 }
2117
2119}
2120
2121
2122void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomFmtPreset( const wxString& aPresetName )
2123{
2124 updateBomFmtPresetSelection( aPresetName );
2125
2126 wxCommandEvent dummy;
2128}
2129
2130
2132{
2133 m_currentBomFmtPreset = nullptr;
2135
2136 if( m_bomFmtPresets.count( aPreset.name ) )
2138
2141
2143 doApplyBomFmtPreset( aPreset );
2144}
2145
2146
2148{
2149 m_bomFmtPresets.clear();
2150 m_bomFmtPresetMRU.clear();
2151
2152 // Load the read-only defaults
2153 for( const BOM_FMT_PRESET& preset : BOM_FMT_PRESET::BuiltInPresets() )
2154 {
2155 m_bomFmtPresets[preset.name] = preset;
2156 m_bomFmtPresets[preset.name].readOnly = true;
2157
2158 m_bomFmtPresetMRU.Add( preset.name );
2159 }
2160}
2161
2162
2164{
2165 m_cbBomFmtPresets->Clear();
2166
2167 int idx = 0;
2168 int default_idx = 0;
2169
2170 for( const auto& [presetName, preset] : m_bomFmtPresets )
2171 {
2172 m_cbBomFmtPresets->Append( wxGetTranslation( presetName ), (void*) &preset );
2173
2174 if( presetName == BOM_FMT_PRESET::CSV().name )
2175 default_idx = idx;
2176
2177 idx++;
2178 }
2179
2180 m_cbBomFmtPresets->Append( wxT( "---" ) );
2181 m_cbBomFmtPresets->Append( _( "Save preset..." ) );
2182 m_cbBomFmtPresets->Append( _( "Delete preset..." ) );
2183
2184 // At least the built-in presets should always be present
2185 wxASSERT( !m_bomFmtPresets.empty() );
2186
2187 m_cbBomFmtPresets->SetSelection( default_idx );
2188 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( default_idx ) );
2189}
2190
2191
2193{
2195
2196 auto it = std::find_if( m_bomFmtPresets.begin(), m_bomFmtPresets.end(),
2197 [&]( const std::pair<const wxString, BOM_FMT_PRESET>& aPair )
2198 {
2199 return ( aPair.second.fieldDelimiter == current.fieldDelimiter
2200 && aPair.second.stringDelimiter == current.stringDelimiter
2201 && aPair.second.refDelimiter == current.refDelimiter
2202 && aPair.second.refRangeDelimiter == current.refRangeDelimiter
2203 && aPair.second.keepTabs == current.keepTabs
2204 && aPair.second.keepLineBreaks == current.keepLineBreaks );
2205 } );
2206
2207 if( it != m_bomFmtPresets.end() )
2208 {
2209 // Select the right m_cbBomFmtPresets item.
2210 // but these items are translated if they are predefined items.
2211 bool do_translate = it->second.readOnly;
2212 wxString text = do_translate ? wxGetTranslation( it->first ) : it->first;
2213
2214 m_cbBomFmtPresets->SetStringSelection( text );
2215 }
2216 else
2217 {
2218 m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 ); // separator
2219 }
2220
2221 int idx = m_cbBomFmtPresets->GetSelection();
2222 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
2223}
2224
2225
2227{
2228 // look at m_userBomFmtPresets to know if aName is a read only preset, or a user preset.
2229 // Read only presets have translated names in UI, so we have to use a translated name in UI selection.
2230 // But for a user preset name we should search for aName (not translated)
2231 wxString ui_label = aName;
2232
2233 for( const auto& [presetName, preset] : m_bomFmtPresets )
2234 {
2235 if( presetName == aName )
2236 {
2237 if( preset.readOnly )
2238 ui_label = wxGetTranslation( aName );
2239
2240 break;
2241 }
2242 }
2243
2244 int idx = m_cbBomFmtPresets->FindString( ui_label );
2245
2246 if( idx >= 0 && m_cbBomFmtPresets->GetSelection() != idx )
2247 {
2248 m_cbBomFmtPresets->SetSelection( idx );
2249 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
2250 }
2251 else if( idx < 0 )
2252 {
2253 m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 ); // separator
2254 }
2255}
2256
2257
2259{
2260 int count = m_cbBomFmtPresets->GetCount();
2261 int index = m_cbBomFmtPresets->GetSelection();
2262
2263 auto resetSelection =
2264 [&]()
2265 {
2267 m_cbBomFmtPresets->SetStringSelection( m_currentBomFmtPreset->name );
2268 else
2269 m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 );
2270 };
2271
2272 if( index == count - 3 )
2273 {
2274 // Separator: reject the selection
2275 resetSelection();
2276 return;
2277 }
2278 else if( index == count - 2 )
2279 {
2280 // Save current state to new preset
2281 wxString name;
2282
2285
2286 wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
2287
2288 if( dlg.ShowModal() != wxID_OK )
2289 {
2290 resetSelection();
2291 return;
2292 }
2293
2294 name = dlg.GetValue();
2295 bool exists = m_bomFmtPresets.count( name );
2296
2297 if( !exists )
2298 {
2300 m_bomFmtPresets[name].readOnly = false;
2301 m_bomFmtPresets[name].name = name;
2302 }
2303
2305
2306 if( !exists )
2307 {
2308 index = m_cbBomFmtPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
2309 }
2310 else if( preset->readOnly )
2311 {
2312 wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
2313 _( "Error" ), wxOK | wxICON_ERROR, this );
2314 resetSelection();
2315 return;
2316 }
2317 else
2318 {
2319 // Ask the user if they want to overwrite the existing preset
2320 if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
2321 {
2322 resetSelection();
2323 return;
2324 }
2325
2326 *preset = GetCurrentBomFmtSettings();
2327 preset->name = name;
2328
2329 index = m_cbBomFmtPresets->FindString( name );
2330
2331 if( m_bomFmtPresetMRU.Index( name ) != wxNOT_FOUND )
2332 m_bomFmtPresetMRU.Remove( name );
2333 }
2334
2335 m_currentBomFmtPreset = preset;
2336 m_cbBomFmtPresets->SetSelection( index );
2337 m_bomFmtPresetMRU.Insert( name, 0 );
2338
2339 return;
2340 }
2341 else if( index == count - 1 )
2342 {
2343 // Delete a preset
2344 wxArrayString headers;
2345 std::vector<wxArrayString> items;
2346
2347 headers.Add( _( "Presets" ) );
2348
2349 for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
2350 {
2351 if( !pair.second.readOnly )
2352 {
2353 wxArrayString item;
2354 item.Add( pair.first );
2355 items.emplace_back( item );
2356 }
2357 }
2358
2359 EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
2360 dlg.SetListLabel( _( "Select preset:" ) );
2361
2362 if( dlg.ShowModal() == wxID_OK )
2363 {
2364 wxString presetName = dlg.GetTextSelection();
2365 int idx = m_cbBomFmtPresets->FindString( presetName );
2366
2367 if( idx != wxNOT_FOUND )
2368 {
2369 m_bomFmtPresets.erase( presetName );
2370
2371 m_cbBomFmtPresets->Delete( idx );
2372 m_currentBomFmtPreset = nullptr;
2373 }
2374
2375 if( m_bomFmtPresetMRU.Index( presetName ) != wxNOT_FOUND )
2376 m_bomFmtPresetMRU.Remove( presetName );
2377 }
2378
2379 resetSelection();
2380 return;
2381 }
2382
2383 auto* preset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( index ) );
2384 m_currentBomFmtPreset = preset;
2385
2386 m_lastSelectedBomFmtPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
2387
2388 if( preset )
2389 {
2390 doApplyBomFmtPreset( *preset );
2392 m_currentBomFmtPreset = preset;
2393
2394 if( !m_currentBomFmtPreset->name.IsEmpty() )
2395 {
2396 if( m_bomFmtPresetMRU.Index( preset->name ) != wxNOT_FOUND )
2397 m_bomFmtPresetMRU.Remove( preset->name );
2398
2399 m_bomFmtPresetMRU.Insert( preset->name, 0 );
2400 }
2401 }
2402}
2403
2404
2406{
2407 m_textFieldDelimiter->ChangeValue( aPreset.fieldDelimiter );
2408 m_textStringDelimiter->ChangeValue( aPreset.stringDelimiter );
2409 m_textRefDelimiter->ChangeValue( aPreset.refDelimiter );
2410 m_textRefRangeDelimiter->ChangeValue( aPreset.refRangeDelimiter );
2411 m_checkKeepTabs->SetValue( aPreset.keepTabs );
2412 m_checkKeepLineBreaks->SetValue( aPreset.keepLineBreaks );
2413
2414 // Refresh the preview if that's the current page
2415 if( m_nbPages->GetSelection() == 1 )
2417}
2418
2419
2421{
2422 bool modified = false;
2423
2424 // Save our BOM presets
2425 std::vector<BOM_PRESET> presets;
2426
2427 for( const auto& [name, preset] : m_bomPresets )
2428 {
2429 if( !preset.readOnly )
2430 presets.emplace_back( preset );
2431 }
2432
2433 if( m_schSettings.m_BomPresets != presets )
2434 {
2435 modified = true;
2436 m_schSettings.m_BomPresets = presets;
2437 }
2438
2439 if( m_schSettings.m_BomSettings != m_dataModel->GetBomSettings() && !m_job )
2440 {
2441 modified = true;
2442 m_schSettings.m_BomSettings = m_dataModel->GetBomSettings();
2443 }
2444
2445 // Save our BOM Format presets
2446 std::vector<BOM_FMT_PRESET> fmts;
2447
2448 for( const auto& [name, preset] : m_bomFmtPresets )
2449 {
2450 if( !preset.readOnly )
2451 fmts.emplace_back( preset );
2452 }
2453
2454 if( m_schSettings.m_BomFmtPresets != fmts )
2455 {
2456 modified = true;
2457 m_schSettings.m_BomFmtPresets = fmts;
2458 }
2459
2460 if( m_schSettings.m_BomFmtSettings != GetCurrentBomFmtSettings() && !m_job )
2461 {
2462 modified = true;
2463 m_schSettings.m_BomFmtSettings = GetCurrentBomFmtSettings();
2464 }
2465
2466 if( modified )
2467 m_parent->OnModify();
2468}
2469
2470
2471void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsAdded( SCHEMATIC& aSch, std::vector<SCH_ITEM*>& aSchItem )
2472{
2473 std::set<wxString> savedSelection = SaveGridSelection();
2474
2475 SCH_REFERENCE_LIST allRefs;
2476 m_parent->Schematic().Hierarchy().GetSymbols( allRefs );
2477
2478 for( SCH_ITEM* item : aSchItem )
2479 {
2480 if( item->Type() == SCH_SYMBOL_T )
2481 {
2482 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2483
2484 // Don't add power symbols
2485 if( !symbol->IsMissingLibSymbol() && symbol->IsPower() )
2486 continue;
2487
2488 // Add all fields again in case this symbol has a new one
2489 for( SCH_FIELD& field : symbol->GetFields() )
2490 AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
2491
2492 m_dataModel->AddReferences( getSymbolReferences( symbol, allRefs ) );
2493 }
2494 else if( item->Type() == SCH_SHEET_T )
2495 {
2496 std::set<SCH_SYMBOL*> symbols;
2497 SCH_REFERENCE_LIST refs = getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) );
2498
2499 for( SCH_REFERENCE& ref : refs )
2500 symbols.insert( ref.GetSymbol() );
2501
2502 for( SCH_SYMBOL* symbol : symbols )
2503 {
2504 // Add all fields again in case this symbol has a new one
2505 for( SCH_FIELD& field : symbol->GetFields() )
2506 AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
2507 }
2508
2509 m_dataModel->AddReferences( refs );
2510 }
2511 }
2512
2514 m_dataModel->RebuildRows();
2515 RestoreGridSelection( savedSelection );
2517}
2518
2519
2520void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsRemoved( SCHEMATIC& aSch, std::vector<SCH_ITEM*>& aSchItem )
2521{
2522 std::set<wxString> savedSelection = SaveGridSelection();
2523
2524 for( SCH_ITEM* item : aSchItem )
2525 {
2526 if( item->Type() == SCH_SYMBOL_T )
2527 m_dataModel->RemoveSymbol( *static_cast<SCH_SYMBOL*>( item ) );
2528 else if( item->Type() == SCH_SHEET_T )
2529 m_dataModel->RemoveReferences( getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) ) );
2530 }
2531
2533 m_dataModel->RebuildRows();
2534 RestoreGridSelection( savedSelection );
2536}
2537
2538
2539void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsChanged( SCHEMATIC& aSch, std::vector<SCH_ITEM*>& aSchItem )
2540{
2541 std::set<wxString> savedSelection = SaveGridSelection();
2542
2543 SCH_REFERENCE_LIST allRefs;
2544 m_parent->Schematic().Hierarchy().GetSymbols( allRefs );
2545
2546 for( SCH_ITEM* item : aSchItem )
2547 {
2548 if( item->Type() == SCH_SYMBOL_T )
2549 {
2550 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2551
2552 // Don't add power symbols
2553 if( !symbol->IsMissingLibSymbol() && symbol->IsPower() )
2554 continue;
2555
2556 // Add all fields again in case this symbol has a new one
2557 for( SCH_FIELD& field : symbol->GetFields() )
2558 AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
2559
2560 m_dataModel->UpdateReferences( getSymbolReferences( symbol, allRefs ),
2561 m_parent->Schematic().GetCurrentVariant() );
2562 }
2563 else if( item->Type() == SCH_SHEET_T )
2564 {
2565 std::set<SCH_SYMBOL*> symbols;
2566 SCH_REFERENCE_LIST refs = getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) );
2567
2568 for( SCH_REFERENCE& ref : refs )
2569 symbols.insert( ref.GetSymbol() );
2570
2571 for( SCH_SYMBOL* symbol : symbols )
2572 {
2573 // Add all fields again in case this symbol has a new one
2574 for( SCH_FIELD& field : symbol->GetFields() )
2575 AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
2576 }
2577
2578 m_dataModel->UpdateReferences( refs, m_parent->Schematic().GetCurrentVariant() );
2579 }
2580 }
2581
2583 m_dataModel->RebuildRows();
2584 RestoreGridSelection( savedSelection );
2586}
2587
2588
2590{
2591 m_dataModel->SetPath( aSch.CurrentSheet() );
2592
2593 if( m_dataModel->GetScope() != FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_ALL )
2594 {
2595 std::set<wxString> savedSelection = SaveGridSelection();
2596
2598 m_dataModel->RebuildRows();
2599 RestoreGridSelection( savedSelection );
2601 }
2602}
2603
2604
2606{
2607 m_grid->Connect( wxEVT_GRID_RANGE_SELECTED,
2608 wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected ),
2609 nullptr, this );
2610}
2611
2612
2614{
2615 m_grid->Disconnect( wxEVT_GRID_RANGE_SELECTED,
2616 wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected ),
2617 nullptr, this );
2618}
2619
2620
2622{
2623 std::set<wxString> selectedFullPaths;
2624
2625 wxGridCellCoordsArray topLeft = m_grid->GetSelectionBlockTopLeft();
2626 wxGridCellCoordsArray bottomRight = m_grid->GetSelectionBlockBottomRight();
2627
2628 for( size_t i = 0; i < topLeft.size(); ++i )
2629 {
2630 for( int row = topLeft[i].GetRow(); row <= bottomRight[i].GetRow(); ++row )
2631 {
2632 for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( row ) )
2633 selectedFullPaths.insert( ref.GetFullPath() );
2634 }
2635 }
2636
2637 wxArrayInt selectedRows = m_grid->GetSelectedRows();
2638
2639 for( int row : selectedRows )
2640 {
2641 for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( row ) )
2642 selectedFullPaths.insert( ref.GetFullPath() );
2643 }
2644
2645 int cursorRow = m_grid->GetGridCursorRow();
2646
2647 if( cursorRow >= 0 && selectedFullPaths.empty() )
2648 {
2649 for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( cursorRow ) )
2650 selectedFullPaths.insert( ref.GetFullPath() );
2651 }
2652
2653 return selectedFullPaths;
2654}
2655
2656
2657void DIALOG_SYMBOL_FIELDS_TABLE::RestoreGridSelection( const std::set<wxString>& aFullPaths )
2658{
2659 if( aFullPaths.empty() )
2660 return;
2661
2662 m_grid->ClearSelection();
2663
2664 bool firstSelection = true;
2665
2666 for( int row = 0; row < m_dataModel->GetNumberRows(); ++row )
2667 {
2668 std::vector<SCH_REFERENCE> refs = m_dataModel->GetRowReferences( row );
2669
2670 for( const SCH_REFERENCE& ref : refs )
2671 {
2672 if( aFullPaths.count( ref.GetFullPath() ) )
2673 {
2674 m_grid->SelectRow( row, true );
2675
2676 if( firstSelection )
2677 {
2678 m_grid->SetGridCursor( row, m_grid->GetGridCursorCol() );
2679 firstSelection = false;
2680 }
2681
2682 break;
2683 }
2684 }
2685 }
2686}
2687
2688
2690 SCH_REFERENCE_LIST& aCachedRefs )
2691{
2692 SCH_REFERENCE_LIST symbolRefs;
2693
2694 for( size_t i = 0; i < aCachedRefs.GetCount(); i++ )
2695 {
2696 SCH_REFERENCE& ref = aCachedRefs[i];
2697
2698 if( ref.GetSymbol() == aSymbol )
2699 {
2700 ref.Split(); // Figures out if we are annotated or not
2701 symbolRefs.AddItem( ref );
2702 }
2703 }
2704
2705 return symbolRefs;
2706}
2707
2708
2710{
2711 SCH_SHEET_LIST allSheets = m_parent->Schematic().Hierarchy();
2712 SCH_REFERENCE_LIST sheetRefs;
2713
2714 // We need to operate on all instances of the sheet
2715 for( const SCH_SHEET_INSTANCE& instance : aSheet.GetInstances() )
2716 {
2717 // For every sheet instance we need to get the current schematic sheet
2718 // instance that matches that particular sheet path from the root
2719 for( SCH_SHEET_PATH& basePath : allSheets )
2720 {
2721 if( basePath.Path() == instance.m_Path )
2722 {
2723 SCH_SHEET_PATH sheetPath = basePath;
2724 sheetPath.push_back( &aSheet );
2725
2726 // Create a list of all sheets in this path, starting with the path
2727 // of the sheet that we just deleted, then all of its subsheets
2728 SCH_SHEET_LIST subSheets;
2729 subSheets.push_back( sheetPath );
2730 allSheets.GetSheetsWithinPath( subSheets, sheetPath );
2731
2732 subSheets.GetSymbolsWithinPath( sheetRefs, sheetPath, false, false );
2733 break;
2734 }
2735 }
2736 }
2737
2738 for( SCH_REFERENCE& ref : sheetRefs )
2739 ref.Split();
2740
2741 return sheetRefs;
2742}
2743
2744
2745void DIALOG_SYMBOL_FIELDS_TABLE::onAddVariant( wxCommandEvent& aEvent )
2746{
2747 if( !m_parent->ShowAddVariantDialog() )
2748 return;
2749
2750 wxArrayString ctrlContents;
2751 ctrlContents.Add( GetDefaultVariantName() );
2752
2753 for( const wxString& variant : m_parent->Schematic().GetVariantNames() )
2754 ctrlContents.Add( variant );
2755
2756 ctrlContents.Sort( SortVariantNames );
2757 m_variantListBox->Set( ctrlContents );
2758
2759 wxString currentVariant = m_parent->Schematic().GetCurrentVariant();
2760 int newSelection = m_variantListBox->FindString(
2761 currentVariant.IsEmpty() ? GetDefaultVariantName() : currentVariant );
2762
2763 if( newSelection != wxNOT_FOUND )
2764 m_variantListBox->SetSelection( newSelection );
2765
2767}
2768
2769
2771{
2772 int selection = m_variantListBox->GetSelection();
2773
2774 // An empty or default selection cannot be deleted.
2775 if( ( selection == wxNOT_FOUND ) || ( selection == 0 ) )
2776 {
2777 m_parent->GetInfoBar()->ShowMessageFor( _( "Cannot delete the default variant." ),
2778 10000, wxICON_ERROR );
2779 return;
2780 }
2781
2782 wxString variantName = m_variantListBox->GetString( selection );
2783 m_variantListBox->Delete( selection );
2784 m_parent->Schematic().DeleteVariant( variantName );
2785 m_parent->OnModify();
2786
2787 int newSelection = std::max( 0, selection - 1 );
2788 m_variantListBox->SetSelection( newSelection );
2789
2790 wxString selectedVariant = getSelectedVariant();
2791 m_parent->SetCurrentVariant( selectedVariant );
2792
2793 if( m_grid->CommitPendingChanges( true ) )
2794 {
2795 m_dataModel->SetCurrentVariant( selectedVariant );
2796 m_dataModel->UpdateReferences( m_dataModel->GetReferenceList(), selectedVariant );
2797 m_dataModel->RebuildRows();
2798
2799 if( m_nbPages->GetSelection() == 1 )
2801 else
2802 m_grid->ForceRefresh();
2803 }
2804
2806 m_parent->UpdateVariantSelectionCtrl( m_parent->Schematic().GetVariantNamesForUI() );
2807}
2808
2809
2811{
2812 int selection = m_variantListBox->GetSelection();
2813
2814 // An empty or default selection cannot be renamed.
2815 if( ( selection == wxNOT_FOUND ) || ( selection == 0 ) )
2816 {
2817 m_parent->GetInfoBar()->ShowMessageFor( _( "Cannot rename the default variant." ),
2818 10000, wxICON_ERROR );
2819 return;
2820 }
2821
2822 wxString oldVariantName = m_variantListBox->GetString( selection );
2823
2824 wxTextEntryDialog dlg( this, _( "Enter new variant name:" ), _( "Rename Design Variant" ),
2825 oldVariantName, wxOK | wxCANCEL | wxCENTER );
2826
2827 if( dlg.ShowModal() == wxID_CANCEL )
2828 return;
2829
2830 wxString newVariantName = dlg.GetValue().Trim().Trim( false );
2831
2832 // Empty name is not allowed.
2833 if( newVariantName.IsEmpty() )
2834 {
2835 m_parent->GetInfoBar()->ShowMessageFor( _( "Variant name cannot be empty." ), 10000, wxICON_ERROR );
2836 return;
2837 }
2838
2839 // Reserved name is not allowed (case-insensitive).
2840 if( newVariantName.CmpNoCase( GetDefaultVariantName() ) == 0 )
2841 {
2842 m_parent->GetInfoBar()->ShowMessageFor( wxString::Format( _( "'%s' is a reserved variant name." ),
2844 10000, wxICON_ERROR );
2845 return;
2846 }
2847
2848 // Same name (exact match) - nothing to do
2849 if( newVariantName == oldVariantName )
2850 return;
2851
2852 // Duplicate name is not allowed (case-insensitive).
2853 for( const wxString& existingName : m_parent->Schematic().GetVariantNames() )
2854 {
2855 if( existingName.CmpNoCase( newVariantName ) == 0
2856 && existingName.CmpNoCase( oldVariantName ) != 0 )
2857 {
2858 m_parent->GetInfoBar()->ShowMessageFor( wxString::Format( _( "Variant '%s' already exists." ),
2859 existingName ),
2860 0000, wxICON_ERROR );
2861 return;
2862 }
2863 }
2864
2865 m_parent->Schematic().RenameVariant( oldVariantName, newVariantName );
2866 m_parent->OnModify();
2867
2868 wxArrayString ctrlContents = m_variantListBox->GetStrings();
2869 ctrlContents.Remove( oldVariantName );
2870 ctrlContents.Add( newVariantName );
2871 ctrlContents.Sort( SortVariantNames );
2872 m_variantListBox->Set( ctrlContents );
2873
2874 int newSelection = m_variantListBox->FindString( newVariantName );
2875
2876 if( newSelection != wxNOT_FOUND )
2877 m_variantListBox->SetSelection( newSelection );
2878
2880 m_parent->UpdateVariantSelectionCtrl( m_parent->Schematic().GetVariantNamesForUI() );
2881}
2882
2883
2884void DIALOG_SYMBOL_FIELDS_TABLE::onCopyVariant( wxCommandEvent& aEvent )
2885{
2886 int selection = m_variantListBox->GetSelection();
2887
2888 // An empty or default selection cannot be copied.
2889 if( ( selection == wxNOT_FOUND ) || ( selection == 0 ) )
2890 {
2891 m_parent->GetInfoBar()->ShowMessageFor( _( "Cannot copy the default variant." ),
2892 10000, wxICON_ERROR );
2893 return;
2894 }
2895
2896 wxString sourceVariantName = m_variantListBox->GetString( selection );
2897
2898 wxTextEntryDialog dlg( this, _( "Enter name for the copied variant:" ), _( "Copy Design Variant" ),
2899 sourceVariantName + wxS( "_copy" ), wxOK | wxCANCEL | wxCENTER );
2900
2901 if( dlg.ShowModal() == wxID_CANCEL )
2902 return;
2903
2904 wxString newVariantName = dlg.GetValue().Trim().Trim( false );
2905
2906 // Empty name is not allowed.
2907 if( newVariantName.IsEmpty() )
2908 {
2909 m_parent->GetInfoBar()->ShowMessageFor( _( "Variant name cannot be empty." ), 10000, wxICON_ERROR );
2910 return;
2911 }
2912
2913 // Duplicate name is not allowed.
2914 if( m_variantListBox->FindString( newVariantName ) != wxNOT_FOUND )
2915 {
2916 m_parent->GetInfoBar()->ShowMessageFor( wxString::Format( _( "Variant '%s' already exists." ),
2917 newVariantName ),
2918 10000, wxICON_ERROR );
2919 return;
2920 }
2921
2922 m_parent->Schematic().CopyVariant( sourceVariantName, newVariantName );
2923 m_parent->OnModify();
2924
2925 wxArrayString ctrlContents = m_variantListBox->GetStrings();
2926 ctrlContents.Add( newVariantName );
2927 ctrlContents.Sort( SortVariantNames );
2928 m_variantListBox->Set( ctrlContents );
2929
2930 int newSelection = m_variantListBox->FindString( newVariantName );
2931
2932 if( newSelection != wxNOT_FOUND )
2933 m_variantListBox->SetSelection( newSelection );
2934
2936 m_parent->UpdateVariantSelectionCtrl( m_parent->Schematic().GetVariantNamesForUI() );
2937}
2938
2939
2941{
2942 wxString currentVariant;
2943 wxString selectedVariant = getSelectedVariant();
2944
2946
2947 if( m_parent )
2948 {
2949 currentVariant = m_parent->Schematic().GetCurrentVariant();
2950
2951 if( currentVariant != selectedVariant )
2952 m_parent->SetCurrentVariant( selectedVariant );
2953 }
2954
2955 if( currentVariant != selectedVariant )
2956 {
2957 m_grid->CommitPendingChanges( true );
2958
2959 SCH_COMMIT commit( m_parent );
2960
2961 m_dataModel->ApplyData( commit, m_schSettings.m_TemplateFieldNames, currentVariant );
2962
2963 if( !commit.Empty() )
2964 {
2965 commit.Push( wxS( "Symbol Fields Table Edit" ) ); // Push clears the commit buffer.
2966 m_parent->OnModify();
2967 }
2968
2969 // Update the data model's current variant for field highlighting
2970 m_dataModel->SetCurrentVariant( selectedVariant );
2971 m_dataModel->UpdateReferences( m_dataModel->GetReferenceList(), selectedVariant );
2972 m_dataModel->RebuildRows();
2973
2974 if( m_nbPages->GetSelection() == 1 )
2976 else
2977 m_grid->ForceRefresh();
2978
2980 }
2981}
2982
2983
2985{
2986 int selection = m_variantListBox->GetSelection();
2987
2988 // Copy, rename, and delete are only enabled for non-default variant selections
2989 bool canModify = ( selection != wxNOT_FOUND ) && ( selection != 0 );
2990
2991 m_copyVariantButton->Enable( canModify );
2992 m_renameVariantButton->Enable( canModify );
2993 m_deleteVariantButton->Enable( canModify );
2994}
2995
2996
2998{
2999 wxString retv;
3000
3001 int selection = m_variantListBox->GetSelection();
3002
3003 if( ( selection == wxNOT_FOUND ) || ( m_variantListBox->GetString( selection ) == GetDefaultVariantName() ) )
3004 return retv;
3005
3006 return m_variantListBox->GetString( selection );
3007}
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 OnGridMouseMove(wxMouseEvent &aEvent)
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:100
KICAD_T Type() const
Returns the type of object.
Definition eda_item.h:112
EDA_ITEM * GetParent() const
Definition eda_item.h:114
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:411
Holds all the data relating to one schematic.
Definition schematic.h:89
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:188
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:506
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:173
@ SCH_SHEET_T
Definition typeinfo.h:176
Definition of file extensions used in Kicad.