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