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 (C) 2017-2023, 2024 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
26#include <common.h>
27#include <base_units.h>
28#include <bitmaps.h>
29#include <symbol_library.h>
30#include <confirm.h>
31#include <eda_doc.h>
33#include <schematic_settings.h>
34#include <general.h>
35#include <grid_tricks.h>
36#include <string_utils.h>
37#include <kiface_base.h>
38#include <sch_commit.h>
39#include <sch_edit_frame.h>
40#include <sch_reference_list.h>
41#include <schematic.h>
43#include <kiplatform/ui.h>
47#include <widgets/wx_grid.h>
48#include <wx/debug.h>
49#include <wx/ffile.h>
50#include <wx/grid.h>
51#include <wx/textdlg.h>
52#include <wx/filedlg.h>
53#include <wx/msgdlg.h>
56#include <fields_data_model.h>
57#include <eda_list_dialog.h>
58#include <project_sch.h>
59
60wxDEFINE_EVENT( EDA_EVT_CLOSE_DIALOG_SYMBOL_FIELDS_TABLE, wxCommandEvent );
61
62#ifdef __WXMAC__
63#define COLUMN_MARGIN 3
64#else
65#define COLUMN_MARGIN 15
66#endif
67
69
70
71enum
72{
75};
76
78{
79public:
81 wxDataViewListCtrl* aFieldsCtrl,
82 FIELDS_EDITOR_GRID_DATA_MODEL* aDataModel ) :
83 GRID_TRICKS( aGrid ),
84 m_dlg( aParent ),
85 m_fieldsCtrl( aFieldsCtrl ),
86 m_dataModel( aDataModel )
87 {}
88
89protected:
90 void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override
91 {
92 if( m_grid->GetGridCursorCol() == FOOTPRINT_FIELD )
93 {
94 menu.Append( MYID_SELECT_FOOTPRINT, _( "Select Footprint..." ),
95 _( "Browse for footprint" ) );
96 menu.AppendSeparator();
97 }
98 else if( m_grid->GetGridCursorCol() == DATASHEET_FIELD )
99 {
100 menu.Append( MYID_SHOW_DATASHEET, _( "Show Datasheet" ),
101 _( "Show datasheet in browser" ) );
102 menu.AppendSeparator();
103 }
104
105 GRID_TRICKS::showPopupMenu( menu, aEvent );
106 }
107
108 void doPopupSelection( wxCommandEvent& event ) override
109 {
110 if( event.GetId() == MYID_SELECT_FOOTPRINT )
111 {
112 // pick a footprint using the footprint picker.
113 wxString fpid = m_grid->GetCellValue( m_grid->GetGridCursorRow(), FOOTPRINT_FIELD );
114
115 if( KIWAY_PLAYER* frame = m_dlg->Kiway().Player( FRAME_FOOTPRINT_CHOOSER, true, m_dlg ) )
116 {
117 if( frame->ShowModal( &fpid, m_dlg ) )
118 m_grid->SetCellValue( m_grid->GetGridCursorRow(), FOOTPRINT_FIELD, fpid );
119
120 frame->Destroy();
121 }
122 }
123 else if (event.GetId() == MYID_SHOW_DATASHEET )
124 {
125 wxString datasheet_uri = m_grid->GetCellValue( m_grid->GetGridCursorRow(),
127 GetAssociatedDocument( m_dlg, datasheet_uri, &m_dlg->Prj(),
129 }
130 else
131 {
132 // We have grid tricks events to show/hide the columns from the popup menu
133 // and we need to make sure the data model is updated to match the grid,
134 // so do it through our code instead
135 if( event.GetId() >= GRIDTRICKS_FIRST_SHOWHIDE )
136 {
137 // Pop-up column order is the order of the shown fields, not the
138 // fieldsCtrl order
139 int col = event.GetId() - GRIDTRICKS_FIRST_SHOWHIDE;
140
141 bool show = !m_dataModel->GetShowColumn( col );
142
143 // Convert data model column to by iterating over m_fieldsCtrl rows
144 // and finding the matching field name
145 wxString fieldName = m_dataModel->GetColFieldName( col );
146
147 for( int row = 0; row < m_fieldsCtrl->GetItemCount(); row++ )
148 {
149 if( m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN ) == fieldName )
150 {
151 if( m_grid->CommitPendingChanges( false ) )
152 m_fieldsCtrl->SetToggleValue( show, row, SHOW_FIELD_COLUMN );
153
154 break;
155 }
156 }
157 }
158 else
159 {
161 }
162 }
163 }
164
166 wxDataViewListCtrl* m_fieldsCtrl;
168};
169
170
173 m_currentBomPreset( nullptr ),
174 m_lastSelectedBomPreset( nullptr ),
175 m_parent( parent ),
176 m_schSettings( parent->Schematic().Settings() )
177{
178 // Get all symbols from the list of schematic sheets
180
181 m_bRefresh->SetBitmap( KiBitmapBundle( BITMAPS::small_refresh ) );
182 m_bRefreshPreview->SetBitmap( KiBitmapBundle( BITMAPS::small_refresh ) );
183 m_browseButton->SetBitmap( KiBitmapBundle( BITMAPS::small_folder ) );
184
185 m_addFieldButton->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
186 m_removeFieldButton->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
187 m_renameFieldButton->SetBitmap( KiBitmapBundle( BITMAPS::small_edit ) );
188
189 m_removeFieldButton->Enable( false );
190 m_renameFieldButton->Enable( false );
191
192 m_bomPresetsLabel->SetFont( KIUI::GetInfoFont( this ) );
193 m_labelBomExportPresets->SetFont( KIUI::GetInfoFont( this ) );
194
195 m_fieldsCtrl->AppendTextColumn( _( "Field" ), wxDATAVIEW_CELL_INERT, 0, wxALIGN_LEFT, 0 );
196 m_fieldsCtrl->AppendTextColumn( _( "Label" ), wxDATAVIEW_CELL_EDITABLE, 0, wxALIGN_LEFT, 0 );
197 m_fieldsCtrl->AppendToggleColumn( _( "Show" ), wxDATAVIEW_CELL_ACTIVATABLE, 0,
198 wxALIGN_CENTER, 0 );
199 m_fieldsCtrl->AppendToggleColumn( _( "Group By" ), wxDATAVIEW_CELL_ACTIVATABLE, 0,
200 wxALIGN_CENTER, 0 );
201
202 // GTK asserts if the number of columns doesn't match the data, but we still don't want
203 // to display the canonical names. So we'll insert a column for them, but keep it 0 width.
204 m_fieldsCtrl->AppendTextColumn( _( "Name" ), wxDATAVIEW_CELL_INERT, 0, wxALIGN_LEFT, 0 );
205
206 // SetWidth( wxCOL_WIDTH_AUTOSIZE ) fails here on GTK, so we calculate the title sizes and
207 // set the column widths ourselves.
208 wxDataViewColumn* column = m_fieldsCtrl->GetColumn( SHOW_FIELD_COLUMN );
209 m_showColWidth = KIUI::GetTextSize( column->GetTitle(), m_fieldsCtrl ).x + COLUMN_MARGIN;
210 column->SetMinWidth( m_showColWidth );
211
212 column = m_fieldsCtrl->GetColumn( GROUP_BY_COLUMN );
214 column->SetMinWidth( m_groupByColWidth );
215
216 // The fact that we're a list should keep the control from reserving space for the
217 // expander buttons... but it doesn't. Fix by forcing the indent to 0.
218 m_fieldsCtrl->SetIndent( 0 );
219
220 m_filter->SetDescriptiveText( _( "Filter" ) );
222
223 LoadFieldNames(); // loads rows into m_fieldsCtrl and columns into m_dataModel
224
225 // Now that the fields are loaded we can set the initial location of the splitter
226 // based on the list width. Again, SetWidth( wxCOL_WIDTH_AUTOSIZE ) fails us on GTK.
228 m_labelColWidth = 0;
229
230 int colWidth = 0;
231
232 for( int row = 0; row < m_fieldsCtrl->GetItemCount(); ++row )
233 {
234 const wxString& displayName = m_fieldsCtrl->GetTextValue( row, DISPLAY_NAME_COLUMN );
235 colWidth = std::max( colWidth, KIUI::GetTextSize( displayName, m_fieldsCtrl ).x );
236
237 const wxString& label = m_fieldsCtrl->GetTextValue( row, LABEL_COLUMN );
238 colWidth = std::max( colWidth, KIUI::GetTextSize( label, m_fieldsCtrl ).x );
239 }
240
241 m_fieldNameColWidth = colWidth + 20;
242 m_labelColWidth = colWidth + 20;
243
245
246 m_fieldsCtrl->GetColumn( DISPLAY_NAME_COLUMN )->SetWidth( m_fieldNameColWidth );
247 m_fieldsCtrl->GetColumn( LABEL_COLUMN )->SetWidth( m_labelColWidth );
248
249 // This is used for data only. Don't show it to the user.
250 m_fieldsCtrl->GetColumn( FIELD_NAME_COLUMN )->SetHidden( true );
251
252 m_splitterMainWindow->SetMinimumPaneSize( fieldsMinWidth );
253 m_splitterMainWindow->SetSashPosition( fieldsMinWidth + 40 );
254
255 m_grid->UseNativeColHeader( true );
256 m_grid->SetTable( m_dataModel, true );
257
258 // must be done after SetTable(), which appears to re-set it
259 m_grid->SetSelectionMode( wxGrid::wxGridSelectCells );
260
261 // add Cut, Copy, and Paste to wxGrid
262 m_grid->PushEventHandler( new FIELDS_EDITOR_GRID_TRICKS( this, m_grid, m_fieldsCtrl,
263 m_dataModel ) );
264
265 // give a bit more room for comboboxes
266 m_grid->SetDefaultRowSize( m_grid->GetDefaultRowSize() + 4 );
267
268 // Load our BOM view presets
272
273 // Load BOM export format presets
277
279 m_grid->ClearSelection();
280
282
284
287
288 wxSize dlgSize( panelCfg.width > 0 ? panelCfg.width : horizPixelsFromDU( 600 ),
289 panelCfg.height > 0 ? panelCfg.height : vertPixelsFromDU( 300 ) );
290 SetSize( dlgSize );
291
292 m_nbPages->SetSelection( cfg->m_FieldEditorPanel.page );
293
295 {
296 case 0: m_radioHighlight->SetValue( true ); break;
297 case 1: m_radioSelect->SetValue( true ); break;
298 case 2: m_radioOff->SetValue( true ); break;
299 }
300
301 switch( cfg->m_FieldEditorPanel.scope )
302 {
303 case SCOPE::SCOPE_ALL: m_radioProject->SetValue( true ); break;
304 case SCOPE::SCOPE_SHEET: m_radioCurrentSheet->SetValue( true ); break;
305 case SCOPE::SCOPE_SHEET_RECURSIVE: m_radioRecursive->SetValue( true ); break;
306 }
307
309
310 Center();
311
312 // Connect Events
313 m_grid->Connect( wxEVT_GRID_COL_SORT,
314 wxGridEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnColSort ), nullptr, this );
315 m_grid->Connect( wxEVT_GRID_COL_MOVE,
316 wxGridEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnColMove ), nullptr, this );
319 m_fieldsCtrl->Bind( wxEVT_DATAVIEW_ITEM_VALUE_CHANGED,
321
322 // Start listening for schematic changes
323 m_parent->Schematic().AddListener( this );
324}
325
326
328{
329 wxGridCellAttr* attr = new wxGridCellAttr;
330 attr->SetReadOnly( false );
331
332 // Set some column types to specific editors
333 if( m_dataModel->ColIsReference( aCol ) )
334 {
335 attr->SetReadOnly();
336 m_grid->SetColAttr( aCol, attr );
337 }
339 {
340 attr->SetEditor( new GRID_CELL_FPID_EDITOR( this, wxEmptyString ) );
341 m_grid->SetColAttr( aCol, attr );
342 }
344 {
345 // set datasheet column viewer button
346 attr->SetEditor( new GRID_CELL_URL_EDITOR( this, PROJECT_SCH::SchSearchS( &Prj() ),
347 &m_parent->Schematic() ) );
348 m_grid->SetColAttr( aCol, attr );
349 }
350 else if( m_dataModel->ColIsQuantity( aCol ) || m_dataModel->ColIsItemNumber( aCol ) )
351 {
352 attr->SetReadOnly();
353 m_grid->SetColAttr( aCol, attr );
354 m_grid->SetColFormatNumber( aCol );
355 }
356 else if( m_dataModel->ColIsAttribute( aCol ) )
357 {
358 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
359 m_grid->SetColAttr( aCol, attr );
360 m_grid->SetColFormatBool( aCol );
361 }
362 else if( IsTextVar( m_dataModel->GetColFieldName( aCol ) ) )
363 {
364 attr->SetReadOnly();
365 m_grid->SetColAttr( aCol, attr );
366 }
367 else
368 {
369 attr->SetEditor( m_grid->GetDefaultEditor() );
370 m_grid->SetColAttr( aCol, attr );
371 m_grid->SetColFormatCustom( aCol, wxGRID_VALUE_STRING );
372 }
373}
374
375
377{
379 wxSize defaultDlgSize = ConvertDialogToPixels( wxSize( 600, 300 ) );
380
381 // Restore column sorting order and widths
382 m_grid->AutoSizeColumns( false );
383 int sortCol = 0;
384 bool sortAscending = true;
385
386 for( int col = 0; col < m_grid->GetNumberCols(); ++col )
387 {
389
390 if( col == m_dataModel->GetSortCol() )
391 {
392 sortCol = col;
393 sortAscending = m_dataModel->GetSortAsc();
394 }
395 }
396
397 // sync m_grid's column visibilities to Show checkboxes in m_fieldsCtrl
398 for( int i = 0; i < m_fieldsCtrl->GetItemCount(); ++i )
399 {
400 int col = m_dataModel->GetFieldNameCol( m_fieldsCtrl->GetTextValue( i, FIELD_NAME_COLUMN ) );
401
402 if( col == -1 )
403 continue;
404
405 bool show = m_fieldsCtrl->GetToggleValue( i, SHOW_FIELD_COLUMN );
406 m_dataModel->SetShowColumn( col, show );
407
408 if( show )
409 {
410 m_grid->ShowCol( col );
411
412 std::string key( m_dataModel->GetColFieldName( col ).ToUTF8() );
413
414 if( cfg->m_FieldEditorPanel.field_widths.count( key )
415 && ( cfg->m_FieldEditorPanel.field_widths.at( key ) > 0 ) )
416 {
417 m_grid->SetColSize( col, cfg->m_FieldEditorPanel.field_widths.at( key ) );
418 }
419 else
420 {
421 int textWidth = m_dataModel->GetDataWidth( col ) + COLUMN_MARGIN;
422 int maxWidth = defaultDlgSize.x / 3;
423
424 m_grid->SetColSize( col, std::clamp( textWidth, 100, maxWidth ) );
425 }
426 }
427 else
428 {
429 m_grid->HideCol( col );
430 }
431 }
432
433 m_dataModel->SetSorting( sortCol, sortAscending );
434 m_grid->SetSortingColumn( sortCol, sortAscending );
435}
436
437
439{
440 // Disconnect Events
441 m_grid->Disconnect( wxEVT_GRID_COL_SORT,
442 wxGridEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnColSort ), nullptr,
443 this );
444 m_grid->Disconnect( wxEVT_GRID_COL_SORT,
445 wxGridEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnColMove ), nullptr,
446 this );
447
448 // Delete the GRID_TRICKS.
449 m_grid->PopEventHandler( true );
450
451 // we gave ownership of m_dataModel to the wxGrid...
452}
453
454
456{
457 if( !wxDialog::TransferDataFromWindow() )
458 return false;
459
461 EE_SELECTION_TOOL* selectionTool = toolMgr->GetTool<EE_SELECTION_TOOL>();
462 EE_SELECTION& selection = selectionTool->GetSelection();
463 SCH_SYMBOL* symbol = nullptr;
464
465 UpdateScope();
466
467 if( selection.GetSize() == 1 )
468 {
469 EDA_ITEM* item = selection.Front();
470
471 if( item->Type() == SCH_SYMBOL_T )
472 symbol = (SCH_SYMBOL*) item;
473 else if( item->GetParent() && item->GetParent()->Type() == SCH_SYMBOL_T )
474 symbol = (SCH_SYMBOL*) item->GetParent();
475 }
476
477 if( symbol )
478 {
479 for( int row = 0; row < m_dataModel->GetNumberRows(); ++row )
480 {
481 std::vector<SCH_REFERENCE> references = m_dataModel->GetRowReferences( row );
482 bool found = false;
483
484 for( const SCH_REFERENCE& ref : references )
485 {
486 if( ref.GetSymbol() == symbol )
487 {
488 found = true;
489 break;
490 }
491 }
492
493 if( found )
494 {
495 // Find the value column and the reference column if they're shown
496 int valueCol = -1;
497 int refCol = -1;
498 int anyCol = -1;
499
500 for( int col = 0; col < m_dataModel->GetNumberCols(); col++ )
501 {
502 if( m_dataModel->ColIsValue( col ) )
503 valueCol = col;
504 else if( m_dataModel->ColIsReference( col ) )
505 refCol = col;
506 else if( anyCol == -1 && m_dataModel->GetShowColumn( col ) )
507 anyCol = col;
508 }
509
510 if( valueCol != -1 && m_dataModel->GetShowColumn( valueCol ) )
511 m_grid->GoToCell( row, valueCol );
512 else if( refCol != -1 && m_dataModel->GetShowColumn( refCol ) )
513 m_grid->GoToCell( row, refCol );
514 else if( anyCol != -1 )
515 m_grid->GoToCell( row, anyCol );
516
517 break;
518 }
519 }
520 }
521
522 // We don't want table range selection events to happen until we've loaded the data or we
523 // we'll clear our selection as the grid is built before the code above can get the
524 // user's current selection.
526
527 return true;
528}
529
530
532{
534 return false;
535
536 if( !wxDialog::TransferDataFromWindow() )
537 return false;
538
539 SCH_COMMIT commit( m_parent );
540 SCH_SHEET_PATH currentSheet = m_parent->GetCurrentSheet();
541
542 std::function<void( SCH_SYMBOL&, SCH_SHEET_PATH & aPath )> changeHandler =
543 [&commit]( SCH_SYMBOL& aSymbol, SCH_SHEET_PATH& aPath ) -> void
544 {
545 commit.Modify( &aSymbol, aPath.LastScreen() );
546 };
547
548 m_dataModel->ApplyData( changeHandler );
549
550 commit.Push( wxS( "Symbol Fields Table Edit" ) );
551
552 // Reset the view to where we left the user
553 m_parent->SetCurrentSheet( currentSheet );
555 m_parent->Refresh();
556
558
559 return true;
560}
561
562
563void DIALOG_SYMBOL_FIELDS_TABLE::AddField( const wxString& aFieldName, const wxString& aLabelValue,
564 bool show, bool groupBy, bool addedByUser )
565{
566 // Users can add fields with variable names that match the special names in the grid,
567 // e.g. ${QUANTITY} so make sure we don't add them twice
568 for( int i = 0; i < m_fieldsCtrl->GetItemCount(); i++ )
569 {
570 if( m_fieldsCtrl->GetTextValue( i, FIELD_NAME_COLUMN ) == aFieldName )
571 return;
572 }
573
574 m_dataModel->AddColumn( aFieldName, aLabelValue, addedByUser );
575
576 wxVector<wxVariant> fieldsCtrlRow;
577 std::string key( aFieldName.ToUTF8() );
578
579 // Don't change these to emplace_back: some versions of wxWidgets don't support it
580 fieldsCtrlRow.push_back( wxVariant( aFieldName ) );
581 fieldsCtrlRow.push_back( wxVariant( aLabelValue ) );
582 fieldsCtrlRow.push_back( wxVariant( show ) );
583 fieldsCtrlRow.push_back( wxVariant( groupBy ) );
584 fieldsCtrlRow.push_back( wxVariant( aFieldName ) );
585
586 m_fieldsCtrl->AppendItem( fieldsCtrlRow );
587
588 wxGridTableMessage msg( m_dataModel, wxGRIDTABLE_NOTIFY_COLS_APPENDED, 1 );
589 m_grid->ProcessTableMessage( msg );
590}
591
592
594{
595 // Add mandatory fields first
596 for( int i = 0; i < MANDATORY_FIELDS; ++i )
597 {
598 bool show = false;
599 bool groupBy = false;
600
601 switch( i )
602 {
603 case REFERENCE_FIELD:
604 case VALUE_FIELD:
605 case FOOTPRINT_FIELD:
606 show = true;
607 groupBy = true;
608 break;
609 case DATASHEET_FIELD:
610 show = true;
611 groupBy = false;
612 break;
613 }
614
616 TEMPLATE_FIELDNAME::GetDefaultFieldName( i, true ), show, groupBy );
617 }
618
619 // Generated fields present only in the fields table
622
623 // User fields next
624 std::set<wxString> userFieldNames;
625
626 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
627 {
628 SCH_SYMBOL* symbol = m_symbolsList[ i ].GetSymbol();
629
630 for( int j = MANDATORY_FIELDS; j < symbol->GetFieldCount(); ++j )
631 userFieldNames.insert( symbol->GetFields()[j].GetName() );
632 }
633
634 for( const wxString& fieldName : userFieldNames )
635 AddField( fieldName, GetTextVars( fieldName ), true, false );
636
637 // Add any templateFieldNames which aren't already present in the userFieldNames
638 for( const TEMPLATE_FIELDNAME& templateFieldname :
640 {
641 if( userFieldNames.count( templateFieldname.m_Name ) == 0 )
642 {
643 AddField( templateFieldname.m_Name, GetTextVars( templateFieldname.m_Name ), false,
644 false );
645 }
646 }
647}
648
649
650void DIALOG_SYMBOL_FIELDS_TABLE::OnAddField( wxCommandEvent& event )
651{
652 wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Add Field" ) );
653
654 if( dlg.ShowModal() != wxID_OK )
655 return;
656
657 wxString fieldName = dlg.GetValue();
658
659 if( fieldName.IsEmpty() )
660 {
661 DisplayError( this, _( "Field must have a name." ) );
662 return;
663 }
664
665 for( int i = 0; i < m_dataModel->GetNumberCols(); ++i )
666 {
667 if( fieldName == m_dataModel->GetColFieldName( i ) )
668 {
669 DisplayError( this, wxString::Format( _( "Field name '%s' already in use." ),
670 fieldName ) );
671 return;
672 }
673 }
674
675 AddField( fieldName, GetTextVars( fieldName ), true, false, true );
676
677 SetupColumnProperties( m_dataModel->GetColsCount() - 1 );
678
680 OnModify();
681}
682
683
684void DIALOG_SYMBOL_FIELDS_TABLE::OnRemoveField( wxCommandEvent& event )
685{
686 int col = -1;
687 int row = m_fieldsCtrl->GetSelectedRow();
688
689 // Should never occur: "Remove Field..." button should be disabled if invalid selection
690 // via OnFieldsCtrlSelectionChanged()
691 wxCHECK_RET( row != -1, wxS( "Some user defined field must be selected first" ) );
692 wxCHECK_RET( row >= MANDATORY_FIELDS, wxS( "Mandatory fields cannot be removed" ) );
693
694 wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
695 wxString displayName = m_fieldsCtrl->GetTextValue( row, DISPLAY_NAME_COLUMN );
696
697 wxString confirm_msg = wxString::Format( _( "Are you sure you want to remove the field '%s'?" ),
698 displayName );
699
700 if( !IsOK( this, confirm_msg ) )
701 return;
702
703 for( int i = 0; i < m_dataModel->GetNumberCols(); ++i )
704 {
705 if( fieldName == m_dataModel->GetColFieldName( i ) )
706 col = i;
707 }
708
709 m_fieldsCtrl->DeleteItem( row );
711
712 // Make selection and update the state of "Remove field..." button via OnFieldsCtrlSelectionChanged()
713 // Safe to decrement row index because we always have mandatory fields
714 m_fieldsCtrl->SelectRow( --row );
715
716 if( row < MANDATORY_FIELDS )
717 {
718 m_removeFieldButton->Enable( false );
719 m_renameFieldButton->Enable( false );
720 }
721
722 wxGridTableMessage msg( m_dataModel, wxGRIDTABLE_NOTIFY_COLS_DELETED, col, 1 );
723
724 m_grid->ProcessTableMessage( msg );
725
727 OnModify();
728}
729
730
731void DIALOG_SYMBOL_FIELDS_TABLE::OnRenameField( wxCommandEvent& event )
732{
733 int row = m_fieldsCtrl->GetSelectedRow();
734 wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
735
736 // Should never occur: "Rename Field..." button should be disabled if invalid selection
737 // via OnFieldsCtrlSelectionChanged()
738 wxCHECK_RET( row != -1, wxS( "Some user defined field must be selected first" ) );
739 wxCHECK_RET( row >= MANDATORY_FIELDS, wxS( "Mandatory fields cannot be renamed" ) );
740 wxCHECK_RET( !fieldName.IsEmpty(), wxS( "Field must have a name" ) );
741
742 int col = m_dataModel->GetFieldNameCol( fieldName );
743 wxCHECK_RET( col != -1, wxS( "Existing field name missing from data model" ) );
744
745 wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Rename Field" ) );
746
747 if( dlg.ShowModal() != wxID_OK )
748 return;
749
750 wxString newFieldName = dlg.GetValue();
751
752 // No change, no-op
753 if( newFieldName == fieldName )
754 return;
755
756 // New field name already exists
757 if( m_dataModel->GetFieldNameCol( newFieldName ) != -1 )
758 {
759 wxString confirm_msg = wxString::Format( _( "Field name %s already exists." ),
760 newFieldName );
761 DisplayError( this, confirm_msg );
762 return;
763 }
764
765 m_dataModel->RenameColumn( col, newFieldName );
766 m_fieldsCtrl->SetTextValue( newFieldName, row, DISPLAY_NAME_COLUMN );
767 m_fieldsCtrl->SetTextValue( newFieldName, row, FIELD_NAME_COLUMN );
768 m_fieldsCtrl->SetTextValue( newFieldName, row, LABEL_COLUMN );
769
771 OnModify();
772}
773
774
775void DIALOG_SYMBOL_FIELDS_TABLE::OnFilterText( wxCommandEvent& aEvent )
776{
777 m_dataModel->SetFilter( m_filter->GetValue() );
779 m_grid->ForceRefresh();
780
782}
783
784
786{
787 wxPoint pos = aEvent.GetPosition();
788 wxRect ctrlRect = m_filter->GetScreenRect();
789 int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
790
791 // TODO: restore cursor when mouse leaves the filter field (or is it a MSW bug?)
792 if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
793 SetCursor( wxCURSOR_ARROW );
794 else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
795 SetCursor( wxCURSOR_ARROW );
796 else
797 SetCursor( wxCURSOR_IBEAM );
798}
799
800
802{
803 int row = m_fieldsCtrl->GetSelectedRow();
804
805 if( row >= MANDATORY_FIELDS )
806 {
809 }
810 else
811 {
812 m_removeFieldButton->Enable( false );
813 m_renameFieldButton->Enable( false );
814 }
815}
816
818{
819 wxDataViewItem item = event.GetItem();
820 int row = m_fieldsCtrl->ItemToRow( item );
821 int col = event.GetColumn();
822
823 switch ( col )
824 {
826 {
827 wxString name = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
828 bool value = m_fieldsCtrl->GetToggleValue( row, col );
829 int dataCol = m_dataModel->GetFieldNameCol( name );
830
831 m_dataModel->SetShowColumn( dataCol, value );
832
833 if( dataCol != -1 )
834 {
835 if( value )
836 m_grid->ShowCol( dataCol );
837 else
838 m_grid->HideCol( dataCol );
839 }
840
841 break;
842 }
843
844 case GROUP_BY_COLUMN:
845 {
846 wxString name = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
847 bool value = m_fieldsCtrl->GetToggleValue( row, col );
848 int dataCol = m_dataModel->GetFieldNameCol( name );
849
850 if( m_dataModel->ColIsQuantity( dataCol ) && value )
851 {
852 DisplayError( this, _( "The Quantity column cannot be grouped by." ) );
853
854 value = false;
855 m_fieldsCtrl->SetToggleValue( value, row, col );
856 }
857
858 if( m_dataModel->ColIsItemNumber( dataCol ) && value )
859 {
860 DisplayError( this, _( "The Item Number column cannot be grouped by." ) );
861
862 value = false;
863 m_fieldsCtrl->SetToggleValue( value, row, col );
864 }
865
866 wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
867
868 m_dataModel->SetGroupColumn( m_dataModel->GetFieldNameCol( fieldName ), value );
870 m_grid->ForceRefresh();
871 break;
872 }
873
874 default:
875 break;
876 }
877
879}
880
881
883{
886 m_grid->ForceRefresh();
887
889}
890
891
893{
896 m_grid->ForceRefresh();
897
899}
900
901
903{
906 m_grid->ForceRefresh();
907
909}
910
911
912void DIALOG_SYMBOL_FIELDS_TABLE::OnColSort( wxGridEvent& aEvent )
913{
914 int sortCol = aEvent.GetCol();
915 std::string key( m_dataModel->GetColFieldName( sortCol ).ToUTF8() );
916 bool ascending;
917
918 // Don't sort by item number, it is generated by the sort
919 if( m_dataModel->ColIsItemNumber( sortCol ) )
920 {
921 aEvent.Veto();
922 return;
923 }
924
925 // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the event, and
926 // if we ask it will give us pre-event info.
927 if( m_grid->IsSortingBy( sortCol ) )
928 {
929 // same column; invert ascending
930 ascending = !m_grid->IsSortOrderAscending();
931 }
932 else
933 {
934 // different column; start with ascending
935 ascending = true;
936 }
937
938 m_dataModel->SetSorting( sortCol, ascending );
940 m_grid->ForceRefresh();
941
943}
944
945
946void DIALOG_SYMBOL_FIELDS_TABLE::OnColMove( wxGridEvent& aEvent )
947{
948 int origPos = aEvent.GetCol();
949
950 // Save column widths since the setup function uses the saved config values
952
953 for( int i = 0; i < m_grid->GetNumberCols(); i++ )
954 {
955 if( m_grid->IsColShown( i ) )
956 {
957 std::string fieldName( m_dataModel->GetColFieldName( i ).ToUTF8() );
958 cfg->m_FieldEditorPanel.field_widths[fieldName] = m_grid->GetColSize( i );
959 }
960 }
961
962 CallAfter(
963 [origPos, this]()
964 {
965 int newPos = m_grid->GetColPos( origPos );
966
967 m_dataModel->MoveColumn( origPos, newPos );
968
969 // "Unmove" the column since we've moved the column internally
970 m_grid->ResetColPos();
971
972 // We need to reset all the column attr's to the correct column order
974
975 m_grid->ForceRefresh();
976 } );
977
979}
980
981
982void DIALOG_SYMBOL_FIELDS_TABLE::OnColLabelChange( wxDataViewEvent& aEvent )
983{
984 wxDataViewItem item = aEvent.GetItem();
985 int row = m_fieldsCtrl->ItemToRow( item );
986 wxString label = m_fieldsCtrl->GetTextValue( row, LABEL_COLUMN );
987 wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
988 int col = m_dataModel->GetFieldNameCol( fieldName );
989
990 if( col != -1 )
991 m_dataModel->SetColLabelValue( col, label );
992
994
995 aEvent.Skip();
996
997 m_grid->ForceRefresh();
998}
999
1001{
1002 m_grid->ForceRefresh();
1003 OnModify();
1004}
1005
1006
1007void DIALOG_SYMBOL_FIELDS_TABLE::OnTableColSize( wxGridSizeEvent& aEvent )
1008{
1009 int col = aEvent.GetRowOrCol();
1010 std::string key( m_dataModel->GetColFieldName( col ).ToUTF8() );
1011
1012 aEvent.Skip();
1013
1014 m_grid->ForceRefresh();
1015}
1016
1017
1019{
1021 m_grid->ForceRefresh();
1022}
1023
1025{
1026 UpdateScope();
1027}
1028
1030{
1032
1033 if( m_radioProject->GetValue() )
1034 m_dataModel->SetScope( FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_ALL );
1035 else if( m_radioCurrentSheet->GetValue() )
1036 m_dataModel->SetScope( FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_SHEET );
1037 else if( m_radioRecursive->GetValue() )
1038 m_dataModel->SetScope( FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_SHEET_RECURSIVE );
1039
1041}
1042
1044{
1045 if( m_dataModel->ColIsReference( event.GetCol() ) )
1046 {
1047 m_grid->ClearSelection();
1048
1049 m_dataModel->ExpandCollapseRow( event.GetRow() );
1050 m_grid->SetGridCursor( event.GetRow(), event.GetCol() );
1051 }
1052 else
1053 {
1054 event.Skip();
1055 }
1056}
1057
1058void DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected( wxGridRangeSelectEvent& aEvent )
1059{
1060 // Cross-probing should only work in Edit page
1061 if( m_nbPages->GetSelection() != 0 )
1062 return;
1063
1064 // Multi-select can grab the rows that are expanded child refs, and also the row
1065 // containing the list of all child refs. Make sure we add refs/symbols uniquely
1066 std::set<SCH_REFERENCE> refs;
1067 std::set<SCH_ITEM*> symbols;
1068
1069 // This handler handles selecting and deselecting
1070 if( aEvent.Selecting() )
1071 {
1072 for( int i = aEvent.GetTopRow(); i <= aEvent.GetBottomRow(); i++ )
1073 {
1074 for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( i ) )
1075 refs.insert( ref );
1076 }
1077
1078 for( const SCH_REFERENCE& ref : refs )
1079 symbols.insert( ref.GetSymbol() );
1080 }
1081
1082 if( m_radioHighlight->GetValue() )
1083 {
1085
1086 if( refs.size() > 0 )
1087 {
1088 // Use of full path based on UUID allows select of not yet annotated or duplicated symbols
1089 wxString symbol_path = refs.begin()->GetFullPath();
1090
1091 // Focus only handles one item at this time
1092 editor->FindSymbolAndItem( &symbol_path, nullptr, true, HIGHLIGHT_SYMBOL,
1093 wxEmptyString );
1094 }
1095 else
1096 {
1097 m_parent->FocusOnItem( nullptr );
1098 }
1099 }
1100 else if( m_radioSelect->GetValue() )
1101 {
1103
1104 std::vector<SCH_ITEM*> items( symbols.begin(), symbols.end() );
1105
1106 if( refs.size() > 0 )
1107 selTool->SyncSelection( refs.begin()->GetSheetPath(), nullptr, items );
1108 else
1109 selTool->ClearSelection();
1110 }
1111}
1112
1113
1115{
1116 // TODO: Option to select footprint if FOOTPRINT column selected
1117
1118 event.Skip();
1119}
1120
1121
1123{
1127#ifdef __WXMAC__
1128 // TODO: something in wxWidgets 3.1.x pads checkbox columns with extra space. (It used to
1129 // also be that the width of the column would get set too wide (to 30), but that's patched in
1130 // our local wxWidgets fork.)
1131 width -= 50;
1132#endif
1133
1134 m_fieldNameColWidth = width / 2;
1136
1137 // GTK loses its head and messes these up when resizing the splitter bar:
1138 m_fieldsCtrl->GetColumn( SHOW_FIELD_COLUMN )->SetWidth( m_showColWidth );
1139 m_fieldsCtrl->GetColumn( GROUP_BY_COLUMN )->SetWidth( m_groupByColWidth );
1140
1141 m_fieldsCtrl->GetColumn( FIELD_NAME_COLUMN )->SetHidden( true );
1142 m_fieldsCtrl->GetColumn( DISPLAY_NAME_COLUMN )->SetWidth( m_fieldNameColWidth );
1143 m_fieldsCtrl->GetColumn( LABEL_COLUMN )->SetWidth( m_labelColWidth );
1144
1145 m_fieldsCtrl->Refresh(); // To refresh checkboxes on Windows.
1146
1147 event.Skip();
1148}
1149
1150
1152{
1154 {
1156 ClearModify();
1157 }
1158}
1159
1160
1161void DIALOG_SYMBOL_FIELDS_TABLE::OnPageChanged( wxNotebookEvent& event )
1162{
1164}
1165
1166
1168{
1171}
1172
1173
1175{
1178}
1179
1180
1182{
1183 BOM_FMT_PRESET current;
1184
1185 current.name = m_cbBomFmtPresets->GetStringSelection();
1186 current.fieldDelimiter = m_textFieldDelimiter->GetValue();
1187 current.stringDelimiter = m_textStringDelimiter->GetValue();
1188 current.refDelimiter = m_textRefDelimiter->GetValue();
1189 current.refRangeDelimiter = m_textRefRangeDelimiter->GetValue();
1190 current.keepTabs = m_checkKeepTabs->GetValue();
1191 current.keepLineBreaks = m_checkKeepLineBreaks->GetValue();
1192
1193 return current;
1194}
1195
1196
1198{
1199 m_nbPages->SetSelection( 0 );
1200}
1201
1202
1204{
1205 m_nbPages->SetSelection( 1 );
1206}
1207
1208
1210{
1211 // Build the absolute path of current output directory to preselect it in the file browser.
1212 wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() );
1213 path = Prj().AbsolutePath( path );
1214
1215
1216 // Calculate the export filename
1217 wxFileName fn( Prj().AbsolutePath( m_parent->Schematic().GetFileName() ) );
1218 fn.SetExt( FILEEXT::CsvFileExtension );
1219
1220 wxFileDialog saveDlg( this, _( "Bill of Materials Output File" ), path, fn.GetFullName(),
1221 FILEEXT::CsvFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
1222
1223 if( saveDlg.ShowModal() == wxID_CANCEL )
1224 return;
1225
1226
1227 wxFileName file = wxFileName( saveDlg.GetPath() );
1228 wxString defaultPath = fn.GetPathWithSep();
1229 wxString msg;
1230 msg.Printf( _( "Do you want to use a path relative to\n'%s'?" ), defaultPath );
1231
1232 wxMessageDialog dialog( this, msg, _( "BOM Output File" ),
1233 wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT );
1234
1235 if( dialog.ShowModal() == wxID_YES )
1236 {
1237 if( !file.MakeRelativeTo( defaultPath ) )
1238 {
1239 wxMessageBox( _( "Cannot make path relative (target volume different from schematic "
1240 "file volume)!" ),
1241 _( "BOM Output File" ), wxOK | wxICON_ERROR );
1242 }
1243 }
1244
1245 m_outputFileName->SetValue( file.GetFullPath() );
1246}
1247
1248
1249void DIALOG_SYMBOL_FIELDS_TABLE::OnExport( wxCommandEvent& aEvent )
1250{
1251 if( m_dataModel->IsEdited() )
1252 {
1253 if( OKOrCancelDialog( nullptr, _( "Unsaved data" ),
1254 _( "Changes have not yet been saved. Export unsaved data?" ), "",
1255 _( "OK" ), _( "Cancel" ) )
1256 == wxID_CANCEL )
1257 {
1258 return;
1259 }
1260 }
1261
1262 // Create output directory if it does not exist (also transform it in absolute form).
1263 // Bail if it fails.
1264
1265 std::function<bool( wxString* )> textResolver =
1266 [&]( wxString* token ) -> bool
1267 {
1268 SCHEMATIC& schematic = m_parent->Schematic();
1269
1270 // Handles m_board->GetTitleBlock() *and* m_board->GetProject()
1271 return schematic.ResolveTextVar( &schematic.CurrentSheet(), token, 0 );
1272 };
1273
1274 wxString path = m_outputFileName->GetValue();
1275
1276 if( path.IsEmpty() )
1277 {
1278 DisplayError( this, _( "No output file specified in Export tab." ) );
1279 return;
1280 }
1281
1282 path = ExpandTextVars( path, &textResolver );
1283 path = ExpandEnvVarSubstitutions( path, nullptr );
1284
1285 wxFileName outputFile = wxFileName::FileName( path );
1286 wxString msg;
1287
1288 if( !EnsureFileDirectoryExists( &outputFile,
1289 Prj().AbsolutePath( m_parent->Schematic().GetFileName() ),
1291 {
1292 msg.Printf( _( "Could not open/create path '%s'." ), outputFile.GetPath() );
1293 DisplayError( this, msg );
1294 return;
1295 }
1296
1297 wxFFile out( outputFile.GetFullPath(), "wb" );
1298
1299 if( !out.IsOpened() )
1300 {
1301 msg.Printf( _( "Could not create BOM output '%s'." ), outputFile.GetFullPath() );
1302 DisplayError( this, msg );
1303 return;
1304 }
1305
1307
1308 if( !out.Write( m_textOutput->GetValue() ) )
1309 {
1310 msg.Printf( _( "Could not write BOM output '%s'." ), outputFile.GetFullPath() );
1311 DisplayError( this, msg );
1312 return;
1313 }
1314
1315 out.Close(); // close the file before we tell the user it's done with the info modal :workflow meme:
1316 msg.Printf( _( "Wrote BOM output to '%s'" ), outputFile.GetFullPath() );
1317 DisplayInfoMessage( this, msg );
1318}
1319
1320
1321void DIALOG_SYMBOL_FIELDS_TABLE::OnCancel( wxCommandEvent& aEvent )
1322{
1323 Close();
1324}
1325
1326
1327void DIALOG_SYMBOL_FIELDS_TABLE::OnOk( wxCommandEvent& aEvent )
1328{
1330 Close();
1331}
1332
1333
1334void DIALOG_SYMBOL_FIELDS_TABLE::OnClose( wxCloseEvent& aEvent )
1335{
1336 // This is a cancel, so commit quietly as we're going to throw the results away anyway.
1338
1339 if( m_dataModel->IsEdited() )
1340 {
1341 if( !HandleUnsavedChanges( this, _( "Save changes?" ),
1342 [&]() -> bool
1343 {
1344 return TransferDataFromWindow();
1345 } ) )
1346 {
1347 aEvent.Veto();
1348 return;
1349 }
1350 }
1351
1352 // Stop listening to schematic events
1354
1355 // Save all our settings since we're really closing
1358
1360
1361 cfg->m_FieldEditorPanel.width = GetSize().x;
1362 cfg->m_FieldEditorPanel.height = GetSize().y;
1363 cfg->m_FieldEditorPanel.page = m_nbPages->GetSelection();
1364
1365 if( m_radioHighlight->GetValue() )
1367 else if( m_radioSelect->GetValue() )
1369 else if( m_radioOff->GetValue() )
1371
1372 if( m_radioProject->GetValue() )
1373 cfg->m_FieldEditorPanel.scope = SCOPE::SCOPE_ALL;
1374 else if( m_radioCurrentSheet->GetValue() )
1375 cfg->m_FieldEditorPanel.scope = SCOPE::SCOPE_SHEET;
1376 else if( m_radioRecursive->GetValue() )
1377 cfg->m_FieldEditorPanel.scope = SCOPE::SCOPE_SHEET_RECURSIVE;
1378
1379 for( int i = 0; i < m_grid->GetNumberCols(); i++ )
1380 {
1381 if( m_grid->IsColShown( i ) )
1382 {
1383 std::string fieldName( m_dataModel->GetColFieldName( i ).ToUTF8() );
1384 cfg->m_FieldEditorPanel.field_widths[fieldName] = m_grid->GetColSize( i );
1385 }
1386 }
1387
1388 m_parent->FocusOnItem( nullptr );
1389
1390 wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_CLOSE_DIALOG_SYMBOL_FIELDS_TABLE, wxID_ANY );
1391
1392 if( wxWindow* parent = GetParent() )
1393 wxQueueEvent( parent, evt );
1394}
1395
1396
1398{
1399 std::vector<BOM_PRESET> ret;
1400
1401 for( const std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
1402 {
1403 if( !pair.second.readOnly )
1404 ret.emplace_back( pair.second );
1405 }
1406
1407 return ret;
1408}
1409
1410
1411void DIALOG_SYMBOL_FIELDS_TABLE::SetUserBomPresets( std::vector<BOM_PRESET>& aPresetList )
1412{
1413 // Reset to defaults
1415
1416 for( const BOM_PRESET& preset : aPresetList )
1417 {
1418 if( m_bomPresets.count( preset.name ) )
1419 continue;
1420
1421 m_bomPresets[preset.name] = preset;
1422
1423 m_bomPresetMRU.Add( preset.name );
1424 }
1425
1427}
1428
1429
1430void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomPreset( const wxString& aPresetName )
1431{
1432 updateBomPresetSelection( aPresetName );
1433
1434 wxCommandEvent dummy;
1436}
1437
1438
1440{
1441 if( m_bomPresets.count( aPreset.name ) )
1443 else
1444 m_currentBomPreset = nullptr;
1445
1448 else
1449 m_lastSelectedBomPreset = nullptr;
1450
1451 updateBomPresetSelection( aPreset.name );
1452 doApplyBomPreset( aPreset );
1453}
1454
1455
1457{
1458 m_bomPresets.clear();
1459 m_bomPresetMRU.clear();
1460
1461 // Load the read-only defaults
1462 for( const BOM_PRESET& preset : BOM_PRESET::BuiltInPresets() )
1463 {
1464 m_bomPresets[preset.name] = preset;
1465 m_bomPresets[preset.name].readOnly = true;
1466
1467 m_bomPresetMRU.Add( preset.name );
1468 }
1469}
1470
1471
1473{
1474 m_cbBomPresets->Clear();
1475
1476 // Build the layers preset list.
1477 // By default, the presetAllLayers will be selected
1478 int idx = 0;
1479 int default_idx = 0;
1480
1481 for( std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
1482 {
1483 m_cbBomPresets->Append( wxGetTranslation( pair.first ),
1484 static_cast<void*>( &pair.second ) );
1485
1486 if( pair.first == BOM_PRESET::DefaultEditing().name )
1487 default_idx = idx;
1488
1489 idx++;
1490 }
1491
1492 m_cbBomPresets->Append( wxT( "---" ) );
1493 m_cbBomPresets->Append( _( "Save preset..." ) );
1494 m_cbBomPresets->Append( _( "Delete preset..." ) );
1495
1496 // At least the built-in presets should always be present
1497 wxASSERT( !m_bomPresets.empty() );
1498
1499 // Default preset: all Boms
1500 m_cbBomPresets->SetSelection( default_idx );
1501 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( default_idx ) );
1502}
1503
1504
1506{
1508
1509 auto it = std::find_if( m_bomPresets.begin(), m_bomPresets.end(),
1510 [&]( const std::pair<const wxString, BOM_PRESET>& aPair )
1511 {
1512 const BOM_PRESET& preset = aPair.second;
1513
1514 // Check the simple settings first
1515 if( !( preset.sortAsc == current.sortAsc
1516 && preset.filterString == current.filterString
1517 && preset.groupSymbols == current.groupSymbols
1518 && preset.excludeDNP == current.excludeDNP
1519 && preset.includeExcludedFromBOM
1520 == current.includeExcludedFromBOM ) )
1521 {
1522 return false;
1523 }
1524
1525 // We should compare preset.name and current.name.
1526 // unfortunately current.name is empty because
1527 // m_dataModel->GetBomSettings() does not store the .name member
1528 // So use sortField member as a (not very efficient) auxiliary filter.
1529 // sortField can be translated in m_bomPresets list,
1530 // so current.sortField needs to be translated
1531 // Probably this not efficient and error prone test should be removed (JPC).
1532 if( preset.sortField != wxGetTranslation( current.sortField ) )
1533 return false;
1534
1535 // Only compare shown or grouped fields
1536 std::vector<BOM_FIELD> A, B;
1537
1538 for( const BOM_FIELD& field : preset.fieldsOrdered )
1539 {
1540 if( field.show || field.groupBy )
1541 A.emplace_back( field );
1542 }
1543
1544 for( const BOM_FIELD& field : current.fieldsOrdered )
1545 {
1546 if( field.show || field.groupBy )
1547 B.emplace_back( field );
1548 }
1549
1550 return A == B;
1551 } );
1552
1553 if( it != m_bomPresets.end() )
1554 {
1555 // Select the right m_cbBomPresets item.
1556 // but these items are translated if they are predefined items.
1557 bool do_translate = it->second.readOnly;
1558 wxString text = do_translate ? wxGetTranslation( it->first ) : it->first;
1559 m_cbBomPresets->SetStringSelection( text );
1560 }
1561 else
1562 {
1563 m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 ); // separator
1564 }
1565
1566 m_currentBomPreset = static_cast<BOM_PRESET*>(
1567 m_cbBomPresets->GetClientData( m_cbBomPresets->GetSelection() ) );
1568}
1569
1570
1572{
1573 // look at m_userBomPresets to know if aName is a read only preset, or a user preset.
1574 // Read only presets have translated names in UI, so we have to use
1575 // a translated name in UI selection.
1576 // But for a user preset name we should search for aName (not translated)
1577 wxString ui_label = aName;
1578
1579 for( std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
1580 {
1581 if( pair.first != aName )
1582 continue;
1583
1584 if( pair.second.readOnly == true )
1585 ui_label = wxGetTranslation( aName );
1586
1587 break;
1588 }
1589
1590 int idx = m_cbBomPresets->FindString( ui_label );
1591
1592 if( idx >= 0 && m_cbBomPresets->GetSelection() != idx )
1593 {
1594 m_cbBomPresets->SetSelection( idx );
1595 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( idx ) );
1596 }
1597 else if( idx < 0 )
1598 {
1599 m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 ); // separator
1600 }
1601}
1602
1603
1605{
1606 int count = m_cbBomPresets->GetCount();
1607 int index = m_cbBomPresets->GetSelection();
1608
1609 auto resetSelection =
1610 [&]()
1611 {
1612 if( m_currentBomPreset )
1613 m_cbBomPresets->SetStringSelection( m_currentBomPreset->name );
1614 else
1615 m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 );
1616 };
1617
1618 if( index == count - 3 )
1619 {
1620 // Separator: reject the selection
1621 resetSelection();
1622 return;
1623 }
1624 else if( index == count - 2 )
1625 {
1626 // Save current state to new preset
1627 wxString name;
1628
1631
1632 wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
1633
1634 if( dlg.ShowModal() != wxID_OK )
1635 {
1636 resetSelection();
1637 return;
1638 }
1639
1640 name = dlg.GetValue();
1641 bool exists = m_bomPresets.count( name );
1642
1643 if( !exists )
1644 {
1646 m_bomPresets[name].readOnly = false;
1647 m_bomPresets[name].name = name;
1648 }
1649
1650 BOM_PRESET* preset = &m_bomPresets[name];
1651
1652 if( !exists )
1653 {
1654 index = m_cbBomPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
1655 }
1656 else if( preset->readOnly )
1657 {
1658 wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
1659 _( "Error" ), wxOK | wxICON_ERROR, this );
1660 resetSelection();
1661 return;
1662 }
1663 else
1664 {
1665 // Ask the user if they want to overwrite the existing preset
1666 if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
1667 {
1668 resetSelection();
1669 return;
1670 }
1671
1672 *preset = m_dataModel->GetBomSettings();
1673 preset->name = name;
1674
1675 index = m_cbBomPresets->FindString( name );
1676 m_bomPresetMRU.Remove( name );
1677 }
1678
1679 m_currentBomPreset = preset;
1680 m_cbBomPresets->SetSelection( index );
1681 m_bomPresetMRU.Insert( name, 0 );
1682
1683 return;
1684 }
1685 else if( index == count - 1 )
1686 {
1687 // Delete a preset
1688 wxArrayString headers;
1689 std::vector<wxArrayString> items;
1690
1691 headers.Add( _( "Presets" ) );
1692
1693 for( std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
1694 {
1695 if( !pair.second.readOnly )
1696 {
1697 wxArrayString item;
1698 item.Add( pair.first );
1699 items.emplace_back( item );
1700 }
1701 }
1702
1703 EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
1704 dlg.SetListLabel( _( "Select preset:" ) );
1705
1706 if( dlg.ShowModal() == wxID_OK )
1707 {
1708 wxString presetName = dlg.GetTextSelection();
1709 int idx = m_cbBomPresets->FindString( presetName );
1710
1711 if( idx != wxNOT_FOUND )
1712 {
1713 m_bomPresets.erase( presetName );
1714
1715 m_cbBomPresets->Delete( idx );
1716 m_currentBomPreset = nullptr;
1717
1718 m_bomPresetMRU.Remove( presetName );
1719 }
1720 }
1721
1722 resetSelection();
1723 return;
1724 }
1725
1726 BOM_PRESET* preset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( index ) );
1727 m_currentBomPreset = preset;
1728
1729 m_lastSelectedBomPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
1730
1731 if( preset )
1732 {
1733 doApplyBomPreset( *preset );
1735 m_currentBomPreset = preset;
1736
1737 if( !m_currentBomPreset->name.IsEmpty() )
1738 {
1739 m_bomPresetMRU.Remove( preset->name );
1740 m_bomPresetMRU.Insert( preset->name, 0 );
1741 }
1742 }
1743}
1744
1745
1747{
1748 // Disable rebuilds while we're applying the preset otherwise we'll be
1749 // rebuilding the model constantly while firing off wx events
1751
1752 // Basically, we apply the BOM preset to the data model and then
1753 // update our UI to reflect resulting the data model state, not the preset.
1754 m_dataModel->ApplyBomPreset( aPreset );
1755
1756 // BOM Presets can add, but not remove, columns, so make sure the field control
1757 // grid has all of them before starting
1758 for( int i = 0; i < m_dataModel->GetColsCount(); i++ )
1759 {
1760 const wxString& fieldName( m_dataModel->GetColFieldName( i ) );
1761 bool found = false;
1762
1763 for( int j = 0; j < m_fieldsCtrl->GetItemCount(); j++ )
1764 {
1765 if( m_fieldsCtrl->GetTextValue( j, FIELD_NAME_COLUMN ) == fieldName )
1766 {
1767 found = true;
1768 break;
1769 }
1770 }
1771
1772 // Properties like label, etc. will be added in the next loop
1773 if( !found )
1774 AddField( fieldName, GetTextVars( fieldName ), false, false );
1775 }
1776
1777 // Sync all fields
1778 for( int i = 0; i < m_fieldsCtrl->GetItemCount(); i++ )
1779 {
1780 const wxString& fieldName( m_fieldsCtrl->GetTextValue( i, FIELD_NAME_COLUMN ) );
1781 int col = m_dataModel->GetFieldNameCol( fieldName );
1782
1783 if( col == -1 )
1784 {
1785 wxASSERT_MSG( true, "Fields control has a field not found in the data model." );
1786 continue;
1787 }
1788
1790 std::string fieldNameStr( fieldName.ToUTF8() );
1791
1792 // Set column labels
1793 const wxString& label = m_dataModel->GetColLabelValue( col );
1794 m_fieldsCtrl->SetTextValue( label, i, LABEL_COLUMN );
1795 m_grid->SetColLabelValue( col, label );
1796
1797 if( cfg->m_FieldEditorPanel.field_widths.count( fieldNameStr ) )
1798 m_grid->SetColSize( col, cfg->m_FieldEditorPanel.field_widths.at( fieldNameStr ) );
1799
1800 // Set shown colums
1801 bool show = m_dataModel->GetShowColumn( col );
1802 m_fieldsCtrl->SetToggleValue( show, i, SHOW_FIELD_COLUMN );
1803
1804 if( show )
1805 m_grid->ShowCol( col );
1806 else
1807 m_grid->HideCol( col );
1808
1809 // Set grouped columns
1810 bool groupBy = m_dataModel->GetGroupColumn( col );
1811 m_fieldsCtrl->SetToggleValue( groupBy, i, GROUP_BY_COLUMN );
1812 }
1813
1814 m_grid->SetSortingColumn( m_dataModel->GetSortCol(), m_dataModel->GetSortAsc() );
1816 m_filter->ChangeValue( m_dataModel->GetFilter() );
1819
1821
1822 // This will rebuild all rows and columns in the model such that the order
1823 // and labels are right, then we refresh the shown grid data to match
1826 m_grid->ForceRefresh();
1827}
1828
1829
1830std::vector<BOM_FMT_PRESET> DIALOG_SYMBOL_FIELDS_TABLE::GetUserBomFmtPresets() const
1831{
1832 std::vector<BOM_FMT_PRESET> ret;
1833
1834 for( const std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
1835 {
1836 if( !pair.second.readOnly )
1837 ret.emplace_back( pair.second );
1838 }
1839
1840 return ret;
1841}
1842
1843
1844void DIALOG_SYMBOL_FIELDS_TABLE::SetUserBomFmtPresets( std::vector<BOM_FMT_PRESET>& aPresetList )
1845{
1846 // Reset to defaults
1848
1849 for( const BOM_FMT_PRESET& preset : aPresetList )
1850 {
1851 if( m_bomFmtPresets.count( preset.name ) )
1852 continue;
1853
1854 m_bomFmtPresets[preset.name] = preset;
1855
1856 m_bomFmtPresetMRU.Add( preset.name );
1857 }
1858
1860}
1861
1862
1863void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomFmtPreset( const wxString& aPresetName )
1864{
1865 updateBomFmtPresetSelection( aPresetName );
1866
1867 wxCommandEvent dummy;
1869}
1870
1871
1873{
1874 if( m_bomFmtPresets.count( aPreset.name ) )
1876 else
1877 m_currentBomFmtPreset = nullptr;
1878
1881 : nullptr;
1882
1884 doApplyBomFmtPreset( aPreset );
1885}
1886
1887
1889{
1890 m_bomFmtPresets.clear();
1891 m_bomFmtPresetMRU.clear();
1892
1893 // Load the read-only defaults
1894 for( const BOM_FMT_PRESET& preset : BOM_FMT_PRESET::BuiltInPresets() )
1895 {
1896 m_bomFmtPresets[preset.name] = preset;
1897 m_bomFmtPresets[preset.name].readOnly = true;
1898
1899 m_bomFmtPresetMRU.Add( preset.name );
1900 }
1901}
1902
1903
1905{
1906 m_cbBomFmtPresets->Clear();
1907
1908 // Build the layers preset list.
1909 // By default, the presetAllLayers will be selected
1910 int idx = 0;
1911 int default_idx = 0;
1912
1913 for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
1914 {
1915 m_cbBomFmtPresets->Append( wxGetTranslation( pair.first ),
1916 static_cast<void*>( &pair.second ) );
1917
1918 if( pair.first == BOM_FMT_PRESET::CSV().name )
1919 default_idx = idx;
1920
1921 idx++;
1922 }
1923
1924 m_cbBomFmtPresets->Append( wxT( "---" ) );
1925 m_cbBomFmtPresets->Append( _( "Save preset..." ) );
1926 m_cbBomFmtPresets->Append( _( "Delete preset..." ) );
1927
1928 // At least the built-in presets should always be present
1929 wxASSERT( !m_bomFmtPresets.empty() );
1930
1931 // Default preset: all Boms
1932 m_cbBomFmtPresets->SetSelection( default_idx );
1934 static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( default_idx ) );
1935}
1936
1937
1939{
1941
1942 auto it = std::find_if( m_bomFmtPresets.begin(), m_bomFmtPresets.end(),
1943 [&]( const std::pair<const wxString, BOM_FMT_PRESET>& aPair )
1944 {
1945 return ( aPair.second.fieldDelimiter == current.fieldDelimiter
1946 && aPair.second.stringDelimiter == current.stringDelimiter
1947 && aPair.second.refDelimiter == current.refDelimiter
1948 && aPair.second.refRangeDelimiter == current.refRangeDelimiter
1949 && aPair.second.keepTabs == current.keepTabs
1950 && aPair.second.keepLineBreaks == current.keepLineBreaks );
1951 } );
1952
1953 if( it != m_bomFmtPresets.end() )
1954 {
1955 // Select the right m_cbBomFmtPresets item.
1956 // but these items are translated if they are predefined items.
1957 bool do_translate = it->second.readOnly;
1958 wxString text = do_translate ? wxGetTranslation( it->first ) : it->first;
1959
1960 m_cbBomFmtPresets->SetStringSelection( text );
1961 }
1962 else
1963 {
1964 m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 ); // separator
1965 }
1966
1967 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>(
1968 m_cbBomFmtPresets->GetClientData( m_cbBomFmtPresets->GetSelection() ) );
1969}
1970
1971
1973{
1974 // look at m_userBomFmtPresets to know if aName is a read only preset, or a user preset.
1975 // Read only presets have translated names in UI, so we have to use
1976 // a translated name in UI selection.
1977 // But for a user preset name we should search for aName (not translated)
1978 wxString ui_label = aName;
1979
1980 for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
1981 {
1982 if( pair.first != aName )
1983 continue;
1984
1985 if( pair.second.readOnly == true )
1986 ui_label = wxGetTranslation( aName );
1987
1988 break;
1989 }
1990
1991 int idx = m_cbBomFmtPresets->FindString( ui_label );
1992
1993 if( idx >= 0 && m_cbBomFmtPresets->GetSelection() != idx )
1994 {
1995 m_cbBomFmtPresets->SetSelection( idx );
1997 static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
1998 }
1999 else if( idx < 0 )
2000 {
2001 m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 ); // separator
2002 }
2003}
2004
2005
2007{
2008 int count = m_cbBomFmtPresets->GetCount();
2009 int index = m_cbBomFmtPresets->GetSelection();
2010
2011 auto resetSelection =
2012 [&]()
2013 {
2015 m_cbBomFmtPresets->SetStringSelection( m_currentBomFmtPreset->name );
2016 else
2017 m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 );
2018 };
2019
2020 if( index == count - 3 )
2021 {
2022 // Separator: reject the selection
2023 resetSelection();
2024 return;
2025 }
2026 else if( index == count - 2 )
2027 {
2028 // Save current state to new preset
2029 wxString name;
2030
2033
2034 wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
2035
2036 if( dlg.ShowModal() != wxID_OK )
2037 {
2038 resetSelection();
2039 return;
2040 }
2041
2042 name = dlg.GetValue();
2043 bool exists = m_bomFmtPresets.count( name );
2044
2045 if( !exists )
2046 {
2048 m_bomFmtPresets[name].readOnly = false;
2049 m_bomFmtPresets[name].name = name;
2050 }
2051
2053
2054 if( !exists )
2055 {
2056 index = m_cbBomFmtPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
2057 }
2058 else if( preset->readOnly )
2059 {
2060 wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
2061 _( "Error" ), wxOK | wxICON_ERROR, this );
2062 resetSelection();
2063 return;
2064 }
2065 else
2066 {
2067 // Ask the user if they want to overwrite the existing preset
2068 if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
2069 {
2070 resetSelection();
2071 return;
2072 }
2073
2074 *preset = GetCurrentBomFmtSettings();
2075 preset->name = name;
2076
2077 index = m_cbBomFmtPresets->FindString( name );
2078 m_bomFmtPresetMRU.Remove( name );
2079 }
2080
2081 m_currentBomFmtPreset = preset;
2082 m_cbBomFmtPresets->SetSelection( index );
2083 m_bomFmtPresetMRU.Insert( name, 0 );
2084
2085 return;
2086 }
2087 else if( index == count - 1 )
2088 {
2089 // Delete a preset
2090 wxArrayString headers;
2091 std::vector<wxArrayString> items;
2092
2093 headers.Add( _( "Presets" ) );
2094
2095 for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
2096 {
2097 if( !pair.second.readOnly )
2098 {
2099 wxArrayString item;
2100 item.Add( pair.first );
2101 items.emplace_back( item );
2102 }
2103 }
2104
2105 EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
2106 dlg.SetListLabel( _( "Select preset:" ) );
2107
2108 if( dlg.ShowModal() == wxID_OK )
2109 {
2110 wxString presetName = dlg.GetTextSelection();
2111 int idx = m_cbBomFmtPresets->FindString( presetName );
2112
2113 if( idx != wxNOT_FOUND )
2114 {
2115 m_bomFmtPresets.erase( presetName );
2116
2117 m_cbBomFmtPresets->Delete( idx );
2118 m_currentBomFmtPreset = nullptr;
2119
2120 m_bomFmtPresetMRU.Remove( presetName );
2121 }
2122 }
2123
2124 resetSelection();
2125 return;
2126 }
2127
2128 auto* preset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( index ) );
2129 m_currentBomFmtPreset = preset;
2130
2131 m_lastSelectedBomFmtPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
2132
2133 if( preset )
2134 {
2135 doApplyBomFmtPreset( *preset );
2137 m_currentBomFmtPreset = preset;
2138
2139 if( !m_currentBomFmtPreset->name.IsEmpty() )
2140 {
2141 m_bomFmtPresetMRU.Remove( preset->name );
2142 m_bomFmtPresetMRU.Insert( preset->name, 0 );
2143 }
2144 }
2145}
2146
2147
2149{
2150 m_textFieldDelimiter->ChangeValue( aPreset.fieldDelimiter );
2151 m_textStringDelimiter->ChangeValue( aPreset.stringDelimiter );
2152 m_textRefDelimiter->ChangeValue( aPreset.refDelimiter );
2153 m_textRefRangeDelimiter->ChangeValue( aPreset.refRangeDelimiter );
2154 m_checkKeepTabs->SetValue( aPreset.keepTabs );
2155 m_checkKeepLineBreaks->SetValue( aPreset.keepLineBreaks );
2156
2157
2158 // Refresh the preview if that's the current page
2159 if( m_nbPages->GetSelection() == 1 )
2161}
2162
2163
2165{
2166 bool modified = false;
2167
2168 // Save our BOM presets
2169 std::vector<BOM_PRESET> presets;
2170
2171 for( const std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
2172 {
2173 if( !pair.second.readOnly )
2174 presets.emplace_back( pair.second );
2175 }
2176
2177 if( m_schSettings.m_BomPresets != presets )
2178 {
2179 modified = true;
2180 m_schSettings.m_BomPresets = presets;
2181 }
2182
2184 {
2185 modified = true;
2187 }
2188
2189
2190 // Save our BOM Format presets
2191 std::vector<BOM_FMT_PRESET> fmts;
2192
2193 for( const std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
2194 {
2195 if( !pair.second.readOnly )
2196 fmts.emplace_back( pair.second );
2197 }
2198
2199 if( m_schSettings.m_BomFmtPresets != fmts )
2200 {
2201 modified = true;
2203 }
2204
2206 {
2207 modified = true;
2209 }
2210
2211 if( modified )
2212 m_parent->OnModify();
2213}
2214
2215
2217 std::vector<SCH_ITEM*>& aSchItem )
2218{
2219 SCH_REFERENCE_LIST allRefs;
2220 m_parent->Schematic().Hierarchy().GetSymbols( allRefs );
2221
2222 for( SCH_ITEM* item : aSchItem )
2223 {
2224 if( item->Type() == SCH_SYMBOL_T )
2225 {
2226 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2227
2228 // Don't add power symbols
2229 if( !symbol->IsMissingLibSymbol() && symbol->IsPower() )
2230 continue;
2231
2232 // Add all fields again in case this symbol has a new one
2233 for( SCH_FIELD& field : symbol->GetFields() )
2234 AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
2235
2236 m_dataModel->AddReferences( getSymbolReferences( symbol, allRefs ) );
2237 }
2238 else if( item->Type() == SCH_SHEET_T )
2239 {
2240 std::set<SCH_SYMBOL*> symbols;
2241 SCH_REFERENCE_LIST refs = getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) );
2242
2243 for( SCH_REFERENCE& ref : refs )
2244 symbols.insert( ref.GetSymbol() );
2245
2246 for( SCH_SYMBOL* symbol : symbols )
2247 {
2248 // Add all fields again in case this symbol has a new one
2249 for( SCH_FIELD& field : symbol->GetFields() )
2250 AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
2251 }
2252
2253 m_dataModel->AddReferences( refs );
2254 }
2255 }
2256
2260}
2261
2262
2264 std::vector<SCH_ITEM*>& aSchItem )
2265{
2266 for( SCH_ITEM* item : aSchItem )
2267 {
2268 if( item->Type() == SCH_SYMBOL_T )
2269 {
2270 m_dataModel->RemoveSymbol( *static_cast<SCH_SYMBOL*>( item ) );
2271 }
2272 else if( item->Type() == SCH_SHEET_T )
2273 {
2275 getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) ) );
2276 }
2277 }
2278
2282}
2283
2284
2286 std::vector<SCH_ITEM*>& aSchItem )
2287{
2288 SCH_REFERENCE_LIST allRefs;
2289 m_parent->Schematic().Hierarchy().GetSymbols( allRefs );
2290
2291 for( SCH_ITEM* item : aSchItem )
2292 {
2293 if( item->Type() == SCH_SYMBOL_T )
2294 {
2295 SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
2296
2297 // Don't add power symbols
2298 if( !symbol->IsMissingLibSymbol() && symbol->IsPower() )
2299 continue;
2300
2301 // Add all fields again in case this symbol has a new one
2302 for( SCH_FIELD& field : symbol->GetFields() )
2303 AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
2304
2305 m_dataModel->UpdateReferences( getSymbolReferences( symbol, allRefs ) );
2306 }
2307 else if( item->Type() == SCH_SHEET_T )
2308 {
2309 std::set<SCH_SYMBOL*> symbols;
2310 SCH_REFERENCE_LIST refs = getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) );
2311
2312 for( SCH_REFERENCE& ref : refs )
2313 symbols.insert( ref.GetSymbol() );
2314
2315 for( SCH_SYMBOL* symbol : symbols )
2316 {
2317 // Add all fields again in case this symbol has a new one
2318 for( SCH_FIELD& field : symbol->GetFields() )
2319 AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
2320 }
2321
2323 }
2324 }
2325
2329}
2330
2331
2333{
2334 m_dataModel->SetPath( aSch.CurrentSheet() );
2335
2336 if( m_dataModel->GetScope() != FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_ALL )
2337 {
2341 }
2342}
2343
2344
2346{
2347 m_grid->Connect(
2348 wxEVT_GRID_RANGE_SELECTED,
2349 wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected ),
2350 nullptr, this );
2351}
2352
2353
2355{
2356 m_grid->Disconnect(
2357 wxEVT_GRID_RANGE_SELECTED,
2358 wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected ),
2359 nullptr, this );
2360}
2361
2362
2365 SCH_REFERENCE_LIST& aCachedRefs )
2366{
2367 SCH_REFERENCE_LIST symbolRefs;
2368
2369 for( size_t i = 0; i < aCachedRefs.GetCount(); i++ )
2370 {
2371 SCH_REFERENCE& ref = aCachedRefs[i];
2372
2373 if( ref.GetSymbol() == aSymbol )
2374 {
2375 ref.Split(); // Figures out if we are annotated or not
2376 symbolRefs.AddItem( ref );
2377 }
2378 }
2379
2380 return symbolRefs;
2381}
2382
2383
2385{
2386 SCH_SHEET_LIST allSheets = m_parent->Schematic().Hierarchy();
2387 SCH_REFERENCE_LIST sheetRefs;
2388
2389 // We need to operate on all instances of the sheet
2390 for( const SCH_SHEET_INSTANCE& instance : aSheet.GetInstances() )
2391 {
2392 // For every sheet instance we need to get the current schematic sheet
2393 // instance that matches that particular sheet path from the root
2394 for( SCH_SHEET_PATH& basePath : allSheets )
2395 {
2396 if( basePath.Path() == instance.m_Path )
2397 {
2398 SCH_SHEET_PATH sheetPath = basePath;
2399 sheetPath.push_back( &aSheet );
2400
2401 // Create a list of all sheets in this path, starting with the path
2402 // of the sheet that we just deleted, then all of its subsheets
2403 SCH_SHEET_LIST subSheets;
2404 subSheets.push_back( sheetPath );
2405 allSheets.GetSheetsWithinPath( subSheets, sheetPath );
2406
2407 subSheets.GetSymbolsWithinPath( sheetRefs, sheetPath, false, false );
2408 break;
2409 }
2410 }
2411 }
2412
2413 for( SCH_REFERENCE& ref : sheetRefs )
2414 ref.Split();
2415
2416 return sheetRefs;
2417}
const char * name
Definition: DXF_plotter.cpp:57
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap)
Definition: bitmap.cpp:110
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Create an undo entry for an item that has been already modified.
Definition: commit.h:105
Dialog helper object to sit in the inheritance tree between wxDialog and any class written by wxFormB...
Definition: dialog_shim.h:88
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:102
void SetupStandardButtons(std::map< int, wxString > aLabels={})
int horizPixelsFromDU(int x) const
Convert an integer number of dialog units to pixels, horizontally.
void ClearModify()
void OnModify()
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
int ShowModal() override
Class DIALOG_SYMBOL_FIELDS_TABLE_BASE.
void OnTableColSize(wxGridSizeEvent &event) override
void OnSaveAndContinue(wxCommandEvent &aEvent) override
void OnSchItemsRemoved(SCHEMATIC &aSch, std::vector< SCH_ITEM * > &aSchItem) 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 OnOk(wxCommandEvent &aEvent) override
void OnGroupSymbolsToggled(wxCommandEvent &event) override
void OnColumnItemToggled(wxDataViewEvent &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)
FIELDS_EDITOR_GRID_DATA_MODEL * m_dataModel
void updateBomPresetSelection(const wxString &aName)
void OnTableItemContextMenu(wxGridEvent &event) override
void updateBomFmtPresetSelection(const wxString &aName)
void OnFilterText(wxCommandEvent &aEvent) override
void OnScopeChanged(wxCommandEvent &aEvent) override
void OnRemoveField(wxCommandEvent &event) override
void OnTableCellClick(wxGridEvent &event) override
DIALOG_SYMBOL_FIELDS_TABLE(SCH_EDIT_FRAME *parent)
void OnShowExcludedToggled(wxCommandEvent &event) override
void OnColLabelChange(wxDataViewEvent &aEvent)
void doApplyBomFmtPreset(const BOM_FMT_PRESET &aPreset)
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 OnFieldsCtrlSelectionChanged(wxDataViewEvent &event) override
void OnTableRangeSelected(wxGridRangeSelectEvent &aEvent)
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
BOM_FMT_PRESET GetCurrentBomFmtSettings()
Returns a formatting configuration corresponding to the values in the UI controls of the dialog.
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 OnSizeFieldList(wxSizeEvent &event) override
void doApplyBomPreset(const BOM_PRESET &aPreset)
void OnPageChanged(wxNotebookEvent &event) override
void SetUserBomFmtPresets(std::vector< BOM_FMT_PRESET > &aPresetList)
void OnRegroupSymbols(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 OnExcludeDNPToggled(wxCommandEvent &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:89
KICAD_T Type() const
Returns the type of object.
Definition: eda_item.h:101
EDA_ITEM * GetParent() const
Definition: eda_item.h:103
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_FIELD_EDITOR m_FieldEditorPanel
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.
EE_SELECTION & GetSelection()
wxString GetColFieldName(int aCol)
int GetFieldNameCol(wxString aFieldName)
void ApplyBomPreset(const BOM_PRESET &preset)
std::vector< SCH_REFERENCE > GetRowReferences(int aRow) const
wxString GetColLabelValue(int aCol) override
void SetPath(const SCH_SHEET_PATH &aPath)
void RenameColumn(int aCol, const wxString &newName)
wxString Export(const BOM_FMT_PRESET &settings)
void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser)
void SetSorting(int aCol, bool ascending)
void SetFilter(const wxString &aFilter)
static const wxString ITEM_NUMBER_VARIABLE
void SetIncludeExcludedFromBOM(bool include)
void UpdateReferences(const SCH_REFERENCE_LIST &aRefs)
void ApplyData(std::function< void(SCH_SYMBOL &, SCH_SHEET_PATH &)> symbolChangeHandler)
static const wxString QUANTITY_VARIABLE
void SetGroupColumn(int aCol, bool group)
void RemoveSymbol(const SCH_SYMBOL &aSymbol)
void SetColLabelValue(int aCol, const wxString &aLabel) override
void MoveColumn(int aCol, int aNewPos)
void RemoveReferences(const SCH_REFERENCE_LIST &aRefs)
void SetShowColumn(int aCol, bool show)
void AddReferences(const SCH_REFERENCE_LIST &aRefs)
void showPopupMenu(wxMenu &menu, wxGridEvent &aEvent) override
FIELDS_EDITOR_GRID_TRICKS(DIALOG_SHIM *aParent, WX_GRID *aGrid, wxDataViewListCtrl *aFieldsCtrl, FIELDS_EDITOR_GRID_DATA_MODEL *aDataModel)
void doPopupSelection(wxCommandEvent &event) override
FIELDS_EDITOR_GRID_DATA_MODEL * m_dataModel
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition: grid_tricks.h:61
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.
Definition: grid_tricks.h:125
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
KIWAY & Kiway() const
Return a reference to the KIWAY that this object has an opportunity to participate in.
Definition: kiway_holder.h:55
A wxFrame capable of the OpenProjectFiles function, meaning it can load a portion of a KiCad project.
Definition: kiway_player.h:65
virtual KIWAY_PLAYER * Player(FRAME_T aFrameType, bool doCreate=true, wxTopLevelWindow *aParent=nullptr)
Return the KIWAY_PLAYER* given a FRAME_T.
Definition: kiway.cpp:406
static REPORTER & GetInstance()
Definition: reporter.cpp:115
static SEARCH_STACK * SchSearchS(PROJECT *aProject)
Accessor for Eeschema search stack.
Definition: project_sch.cpp:41
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:359
BOM_FMT_PRESET m_BomFmtSettings
List of stored BOM format presets.
std::vector< BOM_PRESET > m_BomPresets
std::vector< BOM_FMT_PRESET > m_BomFmtPresets
BOM_PRESET m_BomSettings
List of stored BOM presets.
Holds all the data relating to one schematic.
Definition: schematic.h:77
SCH_SHEET_PATH & CurrentSheet() const override
Definition: schematic.h:152
void RemoveListener(SCHEMATIC_LISTENER *aListener)
Remove the specified listener.
Definition: schematic.cpp:799
wxString GetFileName() const override
Helper to retrieve the filename from the root sheet screen.
Definition: schematic.cpp:308
SCH_SHEET_LIST Hierarchy() const override
Return the full schematic flattened hierarchical sheet list.
Definition: schematic.cpp:214
void AddListener(SCHEMATIC_LISTENER *aListener)
Add a listener to the schematic to receive calls whenever something on the schematic has been modifie...
Definition: schematic.cpp:792
bool ResolveTextVar(const SCH_SHEET_PATH *aSheetPath, wxString *token, int aDepth) const
Definition: schematic.cpp:255
void SyncView()
Mark all items for refresh.
EESCHEMA_SETTINGS * eeconfig() const
virtual void Push(const wxString &aMessage=wxT("A commit"), int aCommitFlags=0) override
Revert the commit by restoring the modified items state.
Definition: sch_commit.cpp:432
Handle actions specific to the schematic editor.
Schematic editor (Eeschema) main window.
void OnModify() override
Must be called after a schematic change in order to set the "modify" flag and update other data struc...
SCH_SHEET_PATH & GetCurrentSheet() const
SCHEMATIC & Schematic() const
void SetCurrentSheet(const SCH_SHEET_PATH &aSheet)
bool SaveProject(bool aSaveAs=false)
Save the currently-open schematic (including its hierarchy) and associated project.
void FocusOnItem(SCH_ITEM *aItem)
Instances are attached to a symbol or sheet and provide a place for the symbol's value,...
Definition: sch_field.h:51
Base class for any item which can be embedded within the SCHEMATIC container class,...
Definition: sch_item.h:166
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
size_t GetCount() const
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
A container for handling SCH_SHEET_PATH objects in a flattened hierarchy.
void GetSymbols(SCH_REFERENCE_LIST &aReferences, bool aIncludePowerSymbols=true, bool aForceIncludeOrphanSymbols=false) const
Add a SCH_REFERENCE object to aReferences for each symbol in the list of sheets.
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...
SCH_SCREEN * LastScreen()
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:57
const std::vector< SCH_SHEET_INSTANCE > & GetInstances() const
Definition: sch_sheet.h:420
Schematic symbol object.
Definition: sch_symbol.h:104
int GetFieldCount() const
Return the number of fields in this symbol.
Definition: sch_symbol.h:606
bool IsMissingLibSymbol() const
Check to see if the library symbol is set to the dummy library symbol.
Definition: sch_symbol.cpp:237
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly)
Populate a std::vector with SCH_FIELDs.
Definition: sch_symbol.cpp:987
bool IsPower() const override
bool Enable(bool aEnable=true) override
void SetBitmap(const wxBitmapBundle &aBmp)
const TEMPLATE_FIELDNAMES & GetTemplateFieldNames()
Return a template field name list for read only access.
TOOL_MANAGER * GetToolManager() const
Return the MVC controller.
Definition: tools_holder.h:55
Master controller class:
Definition: tool_manager.h:62
void SetTable(wxGridTableBase *table, bool aTakeOwnership=false)
Hide wxGrid's SetTable() method with one which doesn't mess up the grid column widths when setting th...
Definition: wx_grid.cpp:270
bool CommitPendingChanges(bool aQuietMode=false)
Close any open cell edit controls.
Definition: wx_grid.cpp:637
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition: common.cpp:348
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, int aFlags)
Definition: common.cpp:59
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:369
bool IsTextVar(const wxString &aSource)
Returns true if the string is a text var, e.g starts with ${.
Definition: common.cpp:133
wxString GetTextVars(const wxString &aSource)
Returns any variables unexpanded, e.g.
Definition: common.cpp:121
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:143
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition: confirm.cpp:250
void DisplayError(wxWindow *aParent, const wxString &aText, int aDisplayTime)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:170
void DisplayInfoMessage(wxWindow *aParent, const wxString &aMessage, const wxString &aExtraInfo)
Display an informational message box with aMessage.
Definition: confirm.cpp:222
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:130
This file is part of the common library.
wxDEFINE_EVENT(EDA_EVT_CLOSE_DIALOG_SYMBOL_FIELDS_TABLE, wxCommandEvent)
#define COLUMN_MARGIN
#define _(s)
bool GetAssociatedDocument(wxWindow *aParent, const wxString &aDocName, PROJECT *aProject, SEARCH_STACK *aPaths, EMBEDDED_FILES *aFiles)
Open a document (file) with the suitable browser.
Definition: eda_doc.cpp:62
This file is part of the common library.
#define LABEL_COLUMN
#define DISPLAY_NAME_COLUMN
#define FIELD_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()
wxSize GetUnobscuredSize(const wxWindow *aWindow)
Tries to determine the size of the viewport of a scrollable widget (wxDataViewCtrl,...
Definition: wxgtk/ui.cpp:195
KICOMMON_API wxFont GetInfoFont(wxWindow *aWindow)
Definition: ui_common.cpp:154
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:77
@ HIGHLIGHT_SYMBOL
std::vector< FAB_LAYER_COLOR > dummy
wxString fieldDelimiter
Definition: bom_settings.h:83
wxString name
Definition: bom_settings.h:81
static BOM_FMT_PRESET CSV()
static std::vector< BOM_FMT_PRESET > BuiltInPresets()
wxString stringDelimiter
Definition: bom_settings.h:84
wxString refRangeDelimiter
Definition: bom_settings.h:86
wxString refDelimiter
Definition: bom_settings.h:85
wxString name
Definition: bom_settings.h:51
static BOM_PRESET DefaultEditing()
wxString sortField
Definition: bom_settings.h:54
std::vector< BOM_FIELD > fieldsOrdered
Definition: bom_settings.h:53
bool readOnly
Definition: bom_settings.h:52
static std::vector< BOM_PRESET > BuiltInPresets()
std::map< std::string, int > field_widths
A simple container for sheet instance information.
Hold a name of a symbol's field, field value, and default visibility.
static const wxString GetDefaultFieldName(int aFieldNdx, bool aTranslateForHI=false)
Return a default symbol field name for field aFieldNdx for all components.
Definition for symbol library class.
wxString GetCanonicalFieldName(int idx)
@ DATASHEET_FIELD
name of datasheet
@ FOOTPRINT_FIELD
Field Name Module PCB, i.e. "16DIP300".
@ VALUE_FIELD
Field Value of part, i.e. "3.3K".
@ MANDATORY_FIELDS
The first 5 are mandatory, and must be instantiated in SCH_COMPONENT and LIB_PART constructors.
@ REFERENCE_FIELD
Field Reference of part, i.e. "IC21".
@ SCH_SYMBOL_T
Definition: typeinfo.h:172
@ SCH_SHEET_T
Definition: typeinfo.h:174
Definition of file extensions used in Kicad.