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