KiCad PCB EDA Suite
Loading...
Searching...
No Matches
dialog_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) Mike Williams
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * 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, see <https://www.gnu.org/licenses/>.
19 */
20
21#include "dialog_fields_table.h"
23
24#include <algorithm>
25#include <functional>
26#include <string>
27#include <bitmaps.h>
28#include <common.h>
29#include <confirm.h>
30#include <core/kicad_algo.h>
31#include <eda_base_frame.h>
32#include <eda_list_dialog.h>
34#include <grid_tricks.h>
35#include <jobs/job_export_bom.h>
36#include <kiplatform/ui.h>
37#include <reporter.h>
39#include <template_fieldnames.h>
40#include <validators.h>
46#include <widgets/ui_common.h>
47#include <wx/ffile.h>
48#include <wx/filedlg.h>
49#include <wx/filename.h>
50#include <wx/grid.h>
51#include <wx/msgdlg.h>
52#include <wx/textdlg.h>
53
54#ifdef __WXMAC__
55#define COLUMN_MARGIN 4
56#else
57#define COLUMN_MARGIN 15
58#endif
59
60
61namespace
62{
63template <typename PRESET>
64int findPresetIndexByPointer( const wxChoice* aChoice, const PRESET* aPreset )
65{
66 for( unsigned i = 0; i < aChoice->GetCount(); ++i )
67 {
68 if( aChoice->GetClientData( i ) == static_cast<const void*>( aPreset ) )
69 return static_cast<int>( i );
70 }
71
72 return wxNOT_FOUND;
73}
74
75
76template <typename PRESET>
77int findPresetIndexByName( const wxChoice* aChoice, const std::map<wxString, PRESET>& aPresets,
78 const wxString& aName )
79{
80 auto presetIt = aPresets.find( aName );
81
82 return presetIt == aPresets.end() ? wxNOT_FOUND : findPresetIndexByPointer( aChoice, &presetIt->second );
83}
84
85
86enum
87{
88 MYID_REVERT_ROW = GRIDTRICKS_FIRST_CLIENT_ID,
89 MYID_CLEAR_CELL
90};
91
92
93class VIEW_CONTROLS_GRID_TRICKS : public GRID_TRICKS
94{
95public:
96 VIEW_CONTROLS_GRID_TRICKS( WX_GRID* aGrid ) :
97 GRID_TRICKS( aGrid )
98 {
99 }
100
101protected:
102 void doPopupSelection( wxCommandEvent& aEvent ) override
103 {
104 if( aEvent.GetId() >= GRIDTRICKS_FIRST_SHOWHIDE )
105 m_grid->PostSizeEvent();
106
108 }
109};
110} // namespace
111
112
114 FIELDS_TABLE_DATA_MODEL_BASE* aDataModel ) :
115 GRID_TRICKS( aGrid ),
116 m_dialog( aDialog ),
117 m_dataModel( aDataModel )
118{
119}
120
121
122void FIELDS_TABLE_GRID_TRICKS::showPopupMenu( wxMenu& aMenu, wxGridEvent& aEvent )
123{
124 int row = m_grid->GetGridCursorRow();
125 int col = m_grid->GetGridCursorCol();
126
127 wxMenuItem* revertMenu = aMenu.Append( MYID_REVERT_ROW, _( "Revert Row" ),
128 _( "Revert the fields in this row" ) );
129 wxMenuItem* clearMenu = aMenu.Append( MYID_CLEAR_CELL, _( "Clear Cell" ),
130 _( "Clear the cell value" ) );
131 aMenu.AppendSeparator();
132
133 if( row >= 0 && col >= 0 )
134 {
135 revertMenu->Enable( m_grid->IsEditable() && m_dataModel->IsRowEdited( row ) );
136 clearMenu->Enable( m_grid->IsEditable() && m_dataModel->CanClearCell( row, col ) );
137 }
138 else
139 {
140 revertMenu->Enable( false );
141 clearMenu->Enable( false );
142 }
143
144 showFieldsTablePopupMenu( aMenu, aEvent );
145}
146
147
149{
150 if( m_dialog->m_variantListBox->GetCount() < 2 )
151 return;
152
153 // Snapshot the selection before showing the menu, which can change the grid selection.
154 std::vector<wxGridCellCoords> cells;
155 bool hasSelection = m_grid->IsSelection();
156
157 if( m_grid->IsEditable() )
158 {
159 for( int row = 0; row < m_grid->GetNumberRows(); ++row )
160 {
161 for( int col = 0; col < m_grid->GetNumberCols(); ++col )
162 {
163 bool selected = hasSelection ? m_grid->IsInSelection( row, col )
164 : row == m_grid->GetGridCursorRow() && col == m_grid->GetGridCursorCol();
165
166 if( selected && m_grid->IsColShown( col ) && m_dataModel->CanCopyCellFromVariant( row, col ) )
167 {
168 cells.emplace_back( row, col );
169 }
170 }
171 }
172 }
173
174 wxMenu* variantsMenu = new wxMenu;
175
176 for( unsigned int i = 0; i < m_dialog->m_variantListBox->GetCount(); ++i )
177 {
178 wxString name = m_dialog->m_variantListBox->GetString( i );
179 wxString variant = name == GetDefaultVariantName() ? wxString() : name;
180 wxString label = name;
181 label.Replace( wxS( "&" ), wxS( "&&" ) );
182
183 wxMenuItem* item = variantsMenu->Append( wxID_ANY, label );
184 item->Enable( !cells.empty() && variant != m_dataModel->GetCurrentVariant() );
185
186 variantsMenu->Bind(
187 wxEVT_COMMAND_MENU_SELECTED,
188 [this, variant, cells]( wxCommandEvent& )
189 {
190 copyFromVariant( variant, cells );
191 },
192 item->GetId() );
193 }
194
195 aMenu.AppendSeparator();
196 aMenu.AppendSubMenu( variantsMenu, _( "Copy from Variant" ) )->Enable( !cells.empty() );
197}
198
199
200void FIELDS_TABLE_GRID_TRICKS::copyFromVariant( const wxString& aVariant, const std::vector<wxGridCellCoords>& aCells )
201{
202 if( !m_grid->CommitPendingChanges( false ) || aCells.empty() )
203 return;
204
205 for( const wxGridCellCoords& cell : aCells )
206 m_dataModel->CopyCellFromVariant( cell.GetRow(), cell.GetCol(), aVariant );
207
208 if( m_dataModel->IsEdited() )
209 m_dialog->OnModify();
210 else
211 m_dialog->ClearModify();
212
213 // One change event records the entire copy as a single dialog undo step.
214 wxGridEvent event( m_grid->GetId(), wxEVT_GRID_CELL_CHANGED, m_grid, aCells.front().GetRow(),
215 aCells.front().GetCol() );
216 m_grid->GetEventHandler()->ProcessEvent( event );
217 m_grid->ForceRefresh();
218}
219
220
221void FIELDS_TABLE_GRID_TRICKS::doPopupSelection( wxCommandEvent& aEvent )
222{
223 int row = m_grid->GetGridCursorRow();
224 int col = m_grid->GetGridCursorCol();
225
226 if( aEvent.GetId() == MYID_REVERT_ROW )
227 {
228 if( m_grid->CommitPendingChanges( false ) )
229 m_dataModel->RevertRow( row );
230
231 if( m_dataModel->IsEdited() )
232 m_dialog->OnModify();
233 else
234 m_dialog->ClearModify();
235
236 m_grid->ForceRefresh();
237 }
238 else if( aEvent.GetId() == MYID_CLEAR_CELL )
239 {
240 if( m_grid->CommitPendingChanges( false ) )
241 m_dataModel->ClearCell( row, col );
242
243 if( m_dataModel->IsEdited() )
244 m_dialog->OnModify();
245 else
246 m_dialog->ClearModify();
247
248 m_grid->ForceRefresh();
249 }
250 else
251 {
253 }
254}
255
256
257void FIELDS_TABLE_GRID_TRICKS::showFieldsTablePopupMenu( wxMenu& aMenu, wxGridEvent& aEvent )
258{
259 GRID_TRICKS::showPopupMenu( aMenu, aEvent );
260}
261
262
264{
266}
267
268
270 FIELDS_TABLE_BOM_SETTINGS& aBomSettings,
271 JOB_EXPORT_BOM* aJob ) :
272 DIALOG_FIELDS_TABLE_BASE( aParent ),
273 m_cfgDialogSettings( aPanelSettings ),
274 m_cfgBomSettings( aBomSettings ),
275 m_job( aJob )
276{
278 m_bMenu->SetBitmap( KiBitmapBundle( BITMAPS::config ) );
281
285
291
293
294 // Don't save scope, we don't want users opening up the table for the first
295 // on program launch stuck with Selection scope and seeing a blank table
296 OptOut( m_scope );
297
299
300 m_viewControlsGrid->UseNativeColHeader( true );
302
303 // must be done after SetTable(), which appears to re-set it
304 m_viewControlsGrid->SetSelectionMode( wxGrid::wxGridSelectCells );
305
306 // add Cut, Copy, and Paste to wxGrid
307 m_viewControlsGrid->PushEventHandler( new VIEW_CONTROLS_GRID_TRICKS( m_viewControlsGrid ) );
308
309 wxGridCellAttr* attr = new wxGridCellAttr;
310 attr->SetReadOnly( true );
311 m_viewControlsDataModel->SetColAttr( attr, DISPLAY_NAME_COLUMN );
312
313 attr = new wxGridCellAttr;
314 attr->SetRenderer( new wxGridCellBoolRenderer() );
315 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
316 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
317 m_viewControlsDataModel->SetColAttr( attr, SHOW_FIELD_COLUMN );
318
319 attr = new wxGridCellAttr;
320 attr->SetRenderer( new wxGridCellBoolRenderer() );
321 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
322 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
323 m_viewControlsDataModel->SetColAttr( attr, GROUP_BY_COLUMN );
324
325 // Compress the view controls grid. (We want it to look different from the fields grid.)
326 m_viewControlsGrid->SetDefaultRowSize( m_viewControlsGrid->GetDefaultRowSize() - FromDIP( 4 ) );
327
328 m_filter->SetDescriptiveText( _( "Filter" ) );
329
330 m_expanderWidth = m_grid->GetRowLabelSize();
331 m_grid->EnableCursorRowColumnHighlight();
332 m_grid->GetGridWindow()->Bind( wxEVT_MOUSEWHEEL, &DIALOG_FIELDS_TABLE::OnGridMouseWheel, this );
333}
334
335
337{
338 m_grid->GetGridWindow()->Unbind( wxEVT_MOUSEWHEEL, &DIALOG_FIELDS_TABLE::OnGridMouseWheel, this );
339 m_viewControlsGrid->PopEventHandler( true );
340}
341
342
344{
345 m_nbPages->SetSelection( 0 );
346}
347
348
350{
351 m_nbPages->SetSelection( 1 );
352}
353
354
355void DIALOG_FIELDS_TABLE::ShowHideColumn( int aCol, bool aShow )
356{
357 if( aShow )
358 m_grid->ShowCol( aCol );
359 else
360 m_grid->HideCol( aCol );
361
362 getDataModel()->SetShowColumn( aCol, aShow );
363
364 if( getDataModel()->GetFilterScope() == BOM_FILTER_SCOPE::VISIBLE
365 && !getDataModel()->GetFilter().IsEmpty() )
366 {
368 }
369
371
372 if( m_nbPages->GetSelection() == 1 )
374 else
375 m_grid->ForceRefresh();
376
377 OnModify();
378}
379
380
381void DIALOG_FIELDS_TABLE::AddField( const wxString& aFieldName, const wxString& aLabelValue, bool aShow, bool aGroupBy,
382 bool aAddedByUser )
383{
384 // Users can add fields with variable names that match the special names in the grid,
385 // e.g. ${QUANTITY} so make sure we don't add them twice
386 for( int row = 0; row < m_viewControlsDataModel->GetNumberRows(); row++ )
387 {
388 if( FieldNamesAreDuplicates( m_viewControlsDataModel->GetUntranslatedFieldName( row ), aFieldName ) )
389 {
390 return;
391 }
392 }
393
394 getDataModel()->AddColumn( aFieldName, aLabelValue, aAddedByUser );
395
396 m_viewControlsGrid->OnAddRow(
397 [&]() -> std::pair<int, int>
398 {
399 m_viewControlsDataModel->AppendRow( aFieldName, aLabelValue, aShow, aGroupBy );
400
401 return { m_viewControlsDataModel->GetNumberRows() - 1, -1 };
402 } );
403}
404
405
406wxString DIALOG_FIELDS_TABLE::GetDefaultBomFileName( const wxString& aInputFileName )
407{
408 if( aInputFileName.IsEmpty() )
409 return wxEmptyString;
410
411 wxFileName fn( aInputFileName );
412 fn.SetExt( FILEEXT::CsvFileExtension );
413
414 return fn.GetFullPath();
415}
416
417
418void DIALOG_FIELDS_TABLE::loadJobBomPreset( const JOB_EXPORT_BOM& aJob, BOM_PRESET& aPreset )
419{
420 aPreset.name = aJob.m_bomPresetName;
421 aPreset.excludeDNP = aJob.m_excludeDNP;
422 aPreset.filterString = aJob.m_filterString;
423 aPreset.filterScope = aJob.m_filterScope;
424 aPreset.sortAsc = aJob.m_sortAsc;
425 aPreset.sortField = aJob.m_sortField;
426 aPreset.groupSymbols = aJob.m_groupSymbols;
427
428 aPreset.fieldsOrdered.clear();
429
430 size_t i = 0;
431
432 for( const wxString& fieldName : aJob.m_fieldsOrdered )
433 {
434 BOM_FIELD field;
435 field.name = fieldName;
436 field.show = !fieldName.StartsWith( wxT( "__" ), &field.name );
437 field.groupBy = alg::contains( aJob.m_fieldsGroupBy, field.name );
438
439 if( ( aJob.m_fieldsLabels.size() > i ) && !aJob.m_fieldsLabels[i].IsEmpty() )
440 field.label = aJob.m_fieldsLabels[i];
441 else if( IsGeneratedField( field.name ) )
442 field.label = GetGeneratedFieldDisplayName( field.name );
443 else
444 field.label = field.name;
445
446 aPreset.fieldsOrdered.emplace_back( field );
447 i++;
448 }
449}
450
451
453{
454 aPreset.name = aJob.m_bomFmtPresetName;
455 aPreset.fieldDelimiter = aJob.m_fieldDelimiter;
456 aPreset.keepLineBreaks = aJob.m_keepLineBreaks;
457 aPreset.keepTabs = aJob.m_keepTabs;
459 aPreset.refDelimiter = aJob.m_refDelimiter;
461 aPreset.stringDelimiter = aJob.m_stringDelimiter;
462}
463
464
466{
467 aJob.SetConfiguredOutputPath( m_outputFileName->GetValue() );
468 aJob.m_bomFmtPresetName = wxEmptyString;
469 aJob.m_bomPresetName = wxEmptyString;
470
472 aJob.m_fieldDelimiter = fmtSettings.fieldDelimiter;
473 aJob.m_stringDelimiter = fmtSettings.stringDelimiter;
474 aJob.m_refDelimiter = fmtSettings.refDelimiter;
475 aJob.m_refRangeDelimiter = fmtSettings.refRangeDelimiter;
476 aJob.m_keepTabs = fmtSettings.keepTabs;
477 aJob.m_keepLineBreaks = fmtSettings.keepLineBreaks;
479
480 BOM_PRESET presetFields = getDataModel()->GetBomSettings();
481 aJob.m_sortAsc = presetFields.sortAsc;
482 aJob.m_excludeDNP = presetFields.excludeDNP;
483 aJob.m_filterString = presetFields.filterString;
484 aJob.m_filterScope = presetFields.filterScope;
485 aJob.m_sortField = presetFields.sortField;
486 aJob.m_groupSymbols = presetFields.groupSymbols;
487
488 aJob.m_fieldsOrdered.clear();
489 aJob.m_fieldsLabels.clear();
490 aJob.m_fieldsGroupBy.clear();
491
492 for( const BOM_FIELD& modelField : getDataModel()->GetFieldsOrdered() )
493 {
494 if( modelField.show )
495 aJob.m_fieldsOrdered.emplace_back( modelField.name );
496 else
497 aJob.m_fieldsOrdered.emplace_back( wxT( "__" ) + modelField.name );
498
499 aJob.m_fieldsLabels.emplace_back( modelField.label );
500
501 if( modelField.groupBy )
502 aJob.m_fieldsGroupBy.emplace_back( modelField.name );
503 }
504
506}
507
508
509bool DIALOG_FIELDS_TABLE::savePresets( bool aSaveCurrentSettings )
510{
511 bool modified = false;
512
513 std::vector<BOM_PRESET> presets = GetUserBomPresets();
514
515 if( m_cfgBomSettings.m_BomPresets != presets )
516 {
517 modified = true;
518 m_cfgBomSettings.m_BomPresets = presets;
519 }
520
521 BOM_PRESET bomSettings = getDataModel()->GetBomSettings();
522
523 if( aSaveCurrentSettings && m_cfgBomSettings.m_BomSettings != bomSettings )
524 {
525 modified = true;
526 m_cfgBomSettings.m_BomSettings = bomSettings;
527 }
528
529 std::vector<BOM_FMT_PRESET> fmts = GetUserBomFmtPresets();
530
531 if( m_cfgBomSettings.m_BomFmtPresets != fmts )
532 {
533 modified = true;
534 m_cfgBomSettings.m_BomFmtPresets = fmts;
535 }
536
538
539 if( aSaveCurrentSettings && m_cfgBomSettings.m_BomFmtSettings != fmtSettings )
540 {
541 modified = true;
542 m_cfgBomSettings.m_BomFmtSettings = fmtSettings;
543 }
544
545 return modified;
546}
547
548
550{
551 return ConvertDialogToPixels( wxSize( 600, 300 ) );
552}
553
554
556{
557 m_grid->Connect( wxEVT_GRID_RANGE_SELECTED,
558 wxGridRangeSelectEventHandler( DIALOG_FIELDS_TABLE::OnTableRangeSelectionChanged ), nullptr,
559 this );
560}
561
562
564{
565 m_grid->Disconnect( wxEVT_GRID_RANGE_SELECTED,
566 wxGridRangeSelectEventHandler( DIALOG_FIELDS_TABLE::OnTableRangeSelectionChanged ), nullptr,
567 this );
568}
569
570
575
576
578{
579 std::set<int> selectedRows;
580
581 wxGridCellCoordsArray topLeft = m_grid->GetSelectionBlockTopLeft();
582 wxGridCellCoordsArray bottomRight = m_grid->GetSelectionBlockBottomRight();
583
584 for( size_t i = 0; i < topLeft.size(); ++i )
585 {
586 for( int row = topLeft[i].GetRow(); row <= bottomRight[i].GetRow(); ++row )
587 selectedRows.insert( row );
588 }
589
590 wxGridCellCoordsArray selectedCells = m_grid->GetSelectedCells();
591
592 for( size_t i = 0; i < selectedCells.size(); ++i )
593 selectedRows.insert( selectedCells[i].GetRow() );
594
595 for( int row : m_grid->GetSelectedRows() )
596 selectedRows.insert( row );
597
598 int cursorRow = m_grid->GetGridCursorRow();
599
600 if( selectedRows.empty() && cursorRow >= 0 && cursorRow < getDataModel()->GetNumberRows() )
601 selectedRows.insert( cursorRow );
602
603 return selectedRows;
604}
605
606
608{
609 std::set<KIID_PATH> selectedItemKeys;
610
611 for( int row : GetSelectedGridRows() )
612 {
613 for( const KIID_PATH& key : getDataModel()->GetRowItemKeys( row ) )
614 selectedItemKeys.insert( key );
615 }
616
617 return selectedItemKeys;
618}
619
620
621void DIALOG_FIELDS_TABLE::RestoreGridSelection( const std::set<KIID_PATH>& aItemKeys )
622{
623 if( aItemKeys.empty() )
624 return;
625
626 m_grid->ClearSelection();
627
628 bool firstSelection = true;
629
630 for( int row = 0; row < getDataModel()->GetNumberRows(); ++row )
631 {
632 for( const KIID_PATH& key : getDataModel()->GetRowItemKeys( row ) )
633 {
634 if( aItemKeys.count( key ) )
635 {
636 m_grid->SelectRow( row, true );
637
638 if( firstSelection )
639 {
640 m_grid->SetGridCursor( row, m_grid->GetGridCursorCol() );
641 firstSelection = false;
642 }
643
644 break;
645 }
646 }
647 }
648}
649
650
652{
653 bool sidebarCollapsed = m_cfgDialogSettings.sidebar_collapsed;
654 int sashPosition = m_cfgDialogSettings.sash_pos;
655 int variantSashPosition = m_cfgDialogSettings.variant_sash_pos;
656
657 m_viewControlsGrid->ShowHideColumns( "0 1 2 3" );
658
659 CallAfter(
660 [this, sidebarCollapsed, sashPosition, variantSashPosition]()
661 {
662 if( sidebarCollapsed )
664 else
665 m_splitterMainWindow->SetSashPosition( sashPosition );
666
667 setSideBarButtonLook( sidebarCollapsed );
668
669 m_splitter_left->SetSashPosition( variantSashPosition );
670 } );
671}
672
673
675{
676 if( !m_cfgDialogSettings.sidebar_collapsed )
677 m_cfgDialogSettings.sash_pos = m_splitterMainWindow->GetSashPosition();
678
679 m_cfgDialogSettings.variant_sash_pos = m_splitter_left->GetSashPosition();
680}
681
682
684{
685 for( int i = 0; i < m_grid->GetNumberCols(); i++ )
686 {
687 if( m_grid->IsColShown( i ) )
688 {
689 std::string fieldName( getDataModel()->GetColFieldName( i ).ToUTF8() );
690 m_cfgDialogSettings.field_widths[fieldName] = m_grid->GetColSize( i );
691 }
692 }
693}
694
695
696// TODO: this probably needs more validation once reference field editing
697// is actually enabled for non-lib-symbol tables
704
705
707{
708 return new GRID_CELL_FPID_EDITOR( this );
709}
710
711
713{
714 m_grid->EnableEditing( !aReadOnly );
715 m_addFieldButton->Enable( !aReadOnly );
716 m_renameFieldButton->Enable( !aReadOnly );
717 m_removeFieldButton->Enable( !aReadOnly );
718 m_buttonApply->Enable( !aReadOnly );
719}
720
721
726
727
729{
730 wxGridCellAttr* attr = new wxGridCellAttr;
731 attr->SetReadOnly( false );
732
733 // Set some column types to specific editors
734 if( getDataModel()->ColIsItemIdentifier( aCol ) )
735 {
736 attr->SetReadOnly();
737 attr->SetRenderer( new wxGridCellStringRenderer() );
738 getDataModel()->SetColAttr( attr, aCol );
739 }
740 else if( getDataModel()->ColIsReference( aCol ) )
741 {
742 // Keep this after item identifiers so Reference remains read-only in tables where it
743 // identifies the item.
744 attr->SetRenderer( new wxGridCellStringRenderer() );
745 attr->SetEditor( createReferenceEditor() );
746 getDataModel()->SetColAttr( attr, aCol );
747 }
748 else if( getDataModel()->GetColFieldName( aCol ) == GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED ) )
749 {
750 attr->SetEditor( createFootprintEditor() );
751 getDataModel()->SetColAttr( attr, aCol );
752 }
753 else if( getDataModel()->GetColFieldName( aCol ) == GetDefaultFieldName( FIELD_T::DATASHEET, UNTRANSLATED ) )
754 {
755 // set datasheet column viewer button
756 attr->SetEditor( createDatasheetEditor() );
757 getDataModel()->SetColAttr( attr, aCol );
758 }
759 else if( getDataModel()->ColIsQuantity( aCol ) || getDataModel()->ColIsItemNumber( aCol ) )
760 {
761 attr->SetReadOnly();
762 attr->SetAlignment( wxALIGN_RIGHT, wxALIGN_CENTER );
763 attr->SetRenderer( new wxGridCellNumberRenderer() );
764 getDataModel()->SetColAttr( attr, aCol );
765 }
766 else if( getDataModel()->ColIsAttribute( aCol ) )
767 {
768 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
769 attr->SetRenderer( new GRID_CELL_CHECKBOX_RENDERER() );
770 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
771 getDataModel()->SetColAttr( attr, aCol );
772 }
773 else if( getDataModel()->ColIsReadOnly( aCol ) )
774 {
775 attr->SetReadOnly();
776 getDataModel()->SetColAttr( attr, aCol );
777 }
778 else
779 {
780 attr->SetRenderer( new wxGridCellStringRenderer() );
781 attr->SetEditor( m_grid->GetDefaultEditor() );
782 getDataModel()->SetColAttr( attr, aCol );
783 }
784}
785
786
788{
790
791 wxSize defaultDlgSize = GetDefaultDialogSize();
792
793 // Restore column sorting order and widths
794 m_grid->AutoSizeColumns( false );
795 int sortCol = 0;
796 bool sortAscending = true;
797
798 for( int col = 0; col < m_grid->GetNumberCols(); ++col )
799 {
801
802 if( col == getDataModel()->GetSortCol() )
803 {
804 sortCol = col;
805 sortAscending = getDataModel()->GetSortAsc();
806 }
807 }
808
809 // sync m_grid's column visibilities to Show checkboxes in m_viewControlsGrid
810 for( int i = 0; i < m_viewControlsDataModel->GetNumberRows(); ++i )
811 {
812 int col = getDataModel()->GetFieldNameCol( m_viewControlsDataModel->GetUntranslatedFieldName( i ) );
813
814 if( col == -1 )
815 continue;
816
817 bool show = m_viewControlsDataModel->GetValueAsBool( i, SHOW_FIELD_COLUMN );
818 getDataModel()->SetShowColumn( col, show );
819
820 if( show )
821 {
822 m_grid->ShowCol( col );
823
824 std::string key( getDataModel()->GetColFieldName( col ).ToUTF8() );
825
826 if( m_cfgDialogSettings.field_widths.count( key ) && ( m_cfgDialogSettings.field_widths.at( key ) > 0 ) )
827 {
828 m_grid->SetColSize( col, m_cfgDialogSettings.field_widths.at( key ) );
829 }
830 else
831 {
832 int textWidth = getDataModel()->GetColDataWidth( col ) + COLUMN_MARGIN;
833 int maxWidth = defaultDlgSize.x / 3;
834
835 m_grid->SetColSize( col, std::clamp( textWidth, 100, maxWidth ) );
836 }
837 }
838 else
839 {
840 m_grid->HideCol( col );
841 }
842 }
843
844 getDataModel()->SetSorting( sortCol, sortAscending );
845 m_grid->SetSortingColumn( sortCol, sortAscending );
846}
847
848
850{
851 m_grid->SetRowLabelSize( getDataModel()->GetGroupingEnabled() ? m_grid->FromDIP( m_expanderWidth ) : 0 );
852}
853
854
855void DIALOG_FIELDS_TABLE::setSideBarButtonLook( bool aIsLeftPanelCollapsed )
856{
857 // Set bitmap and tooltip according to left panel visibility
858
859 if( aIsLeftPanelCollapsed )
860 {
862 m_sidebarButton->SetToolTip( _( "Expand left panel" ) );
863 }
864 else
865 {
867 m_sidebarButton->SetToolTip( _( "Collapse left panel" ) );
868 }
869}
870
871
872void DIALOG_FIELDS_TABLE::OnSidebarToggle( wxCommandEvent& event )
873{
874 if( m_cfgDialogSettings.sidebar_collapsed )
875 {
876 m_cfgDialogSettings.sidebar_collapsed = false;
878 }
879 else
880 {
881 m_cfgDialogSettings.sash_pos = m_splitterMainWindow->GetSashPosition();
882
883 m_cfgDialogSettings.sidebar_collapsed = true;
885 }
886
887 setSideBarButtonLook( m_cfgDialogSettings.sidebar_collapsed );
888}
889
890
892{
893 m_grid->ForceRefresh();
894}
895
896
897void DIALOG_FIELDS_TABLE::OnTableColSize( wxGridSizeEvent& aEvent )
898{
899 aEvent.Skip();
900
901 m_grid->ForceRefresh();
902}
903
904
905void DIALOG_FIELDS_TABLE::OnTableLabelClick( wxGridEvent& aEvent )
906{
907 int row = aEvent.GetRow();
908
909 // Column headers and the corner retain their normal sorting/selection behavior.
910 if( aEvent.GetCol() != -1 || row < 0 || row >= getDataModel()->GetNumberRows() )
911 {
912 aEvent.Skip();
913 return;
914 }
915
916 ROW_STATE state = getDataModel()->GetRowState( row );
917
918 if( !IsRowCollapsed( state ) && !IsRowExpanded( state ) )
919 {
920 aEvent.Skip();
921 return;
922 }
923
924 if( !m_grid->CommitPendingChanges() )
925 return;
926
927 int col = m_grid->GetGridCursorCol();
928 m_grid->ClearSelection();
930
931 if( col >= 0 && col < m_grid->GetNumberCols() )
932 m_grid->SetGridCursor( row, col );
933
934 // Refresh the parent arrow as well as the inserted/deleted child rows.
935 m_grid->ForceRefresh();
936}
937
938
939void DIALOG_FIELDS_TABLE::OnFilterMouseMoved( wxMouseEvent& aEvent )
940{
941#if defined( __WXOSX__ ) // Doesn't work properly on other ports
942 wxPoint pos = aEvent.GetPosition();
943 wxRect ctrlRect = m_filter->GetScreenRect();
944 int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
945
946 // TODO: restore cursor when mouse leaves the filter field (or is it a MSW bug?)
947 if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
948 SetCursor( wxCURSOR_ARROW );
949 else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
950 SetCursor( wxCURSOR_ARROW );
951 else
952 SetCursor( wxCURSOR_IBEAM );
953#endif
954}
955
956
958{
959 const wxString& showColLabel = m_viewControlsGrid->GetColLabelValue( SHOW_FIELD_COLUMN );
960 const wxString& groupByColLabel = m_viewControlsGrid->GetColLabelValue( GROUP_BY_COLUMN );
961 int showColWidth = KIUI::GetTextSize( showColLabel, m_viewControlsGrid ).x + COLUMN_MARGIN;
962 int groupByColWidth = KIUI::GetTextSize( groupByColLabel, m_viewControlsGrid ).x + COLUMN_MARGIN;
963 int remainingWidth = m_viewControlsGrid->GetSize().GetX() - showColWidth - groupByColWidth;
964
965 m_viewControlsGrid->SetColSize( SHOW_FIELD_COLUMN, showColWidth );
966 m_viewControlsGrid->SetColSize( GROUP_BY_COLUMN, groupByColWidth );
967
968 if( m_viewControlsGrid->IsColShown( DISPLAY_NAME_COLUMN ) && m_viewControlsGrid->IsColShown( LABEL_COLUMN ) )
969 {
970 m_viewControlsGrid->SetColSize( DISPLAY_NAME_COLUMN, std::max( remainingWidth / 2, 60 ) );
971 m_viewControlsGrid->SetColSize( LABEL_COLUMN, std::max( remainingWidth - ( remainingWidth / 2 ), 60 ) );
972 }
973 else if( m_viewControlsGrid->IsColShown( DISPLAY_NAME_COLUMN ) )
974 {
975 m_viewControlsGrid->SetColSize( DISPLAY_NAME_COLUMN, std::max( remainingWidth, 60 ) );
976 }
977 else if( m_viewControlsGrid->IsColShown( LABEL_COLUMN ) )
978 {
979 m_viewControlsGrid->SetColSize( LABEL_COLUMN, std::max( remainingWidth, 60 ) );
980 }
981
982 event.Skip();
983}
984
985
987{
988 int row = aEvent.GetRow();
989
990 wxCHECK( row < m_viewControlsGrid->GetNumberRows(), /* void */ );
991
992 switch( aEvent.GetCol() )
993 {
994 case LABEL_COLUMN:
995 {
996 wxString label = m_viewControlsDataModel->GetValue( row, LABEL_COLUMN );
997 wxString fieldName = m_viewControlsDataModel->GetUntranslatedFieldName( row );
998 int dataCol = getDataModel()->GetFieldNameCol( fieldName );
999
1000 if( dataCol != -1 )
1001 {
1002 getDataModel()->SetColLabelValue( dataCol, label );
1003 m_grid->SetColLabelValue( dataCol, label );
1004
1005 if( m_nbPages->GetSelection() == 1 )
1007 else
1008 m_grid->ForceRefresh();
1009
1011 OnModify();
1012 }
1013
1014 break;
1015 }
1016
1017 case SHOW_FIELD_COLUMN:
1018 {
1019 wxString fieldName = m_viewControlsDataModel->GetUntranslatedFieldName( row );
1020 bool value = m_viewControlsDataModel->GetValueAsBool( row, SHOW_FIELD_COLUMN );
1021 int dataCol = getDataModel()->GetFieldNameCol( fieldName );
1022
1023 if( dataCol != -1 )
1024 ShowHideColumn( dataCol, value );
1025
1026 break;
1027 }
1028
1029 case GROUP_BY_COLUMN:
1030 {
1031 wxString fieldName = m_viewControlsDataModel->GetUntranslatedFieldName( row );
1032 bool value = m_viewControlsDataModel->GetValueAsBool( row, GROUP_BY_COLUMN );
1033 int dataCol = getDataModel()->GetFieldNameCol( fieldName );
1034
1035 if( getDataModel()->ColIsQuantity( dataCol ) && value )
1036 {
1037 DisplayError( this, _( "The Quantity column cannot be grouped by." ) );
1038
1039 value = false;
1040 m_viewControlsDataModel->SetValueAsBool( row, GROUP_BY_COLUMN, value );
1041 m_viewControlsGrid->ForceRefresh();
1042 break;
1043 }
1044
1045 if( getDataModel()->ColIsItemNumber( dataCol ) && value )
1046 {
1047 DisplayError( this, _( "The Item Number column cannot be grouped by." ) );
1048
1049 value = false;
1050 m_viewControlsDataModel->SetValueAsBool( row, GROUP_BY_COLUMN, value );
1051 m_viewControlsGrid->ForceRefresh();
1052 break;
1053 }
1054
1055 getDataModel()->SetGroupColumn( dataCol, value );
1057
1058 if( m_nbPages->GetSelection() == 1 )
1060 else
1061 m_grid->ForceRefresh();
1062
1064 OnModify();
1065 break;
1066 }
1067
1068 default: break;
1069 }
1070}
1071
1072
1073void DIALOG_FIELDS_TABLE::OnAddField( wxCommandEvent& aEvent )
1074{
1075 wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Add Field" ) );
1076
1077 if( dlg.ShowModal() != wxID_OK )
1078 return;
1079
1080 wxString fieldName = dlg.GetValue();
1081
1082 if( fieldName.IsEmpty() )
1083 {
1084 DisplayError( this, _( "Field must have a name." ) );
1085 return;
1086 }
1087
1088 for( int i = 0; i < getDataModel()->GetNumberCols(); ++i )
1089 {
1090 if( FieldNamesAreDuplicates( fieldName, getDataModel()->GetColFieldName( i ) ) )
1091 {
1092 DisplayError( this, wxString::Format( _( "Field name '%s' already in use." ), fieldName ) );
1093 return;
1094 }
1095 }
1096
1097 AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), true, false, true );
1098
1099 SetupColumnProperties( getDataModel()->GetColsCount() - 1 );
1100
1102 OnModify();
1103}
1104
1105
1106void DIALOG_FIELDS_TABLE::OnRemoveField( wxCommandEvent& aEvent )
1107{
1108 m_viewControlsGrid->OnDeleteRows(
1109 [&]( int row )
1110 {
1111 for( FIELD_T id : MANDATORY_FIELDS )
1112 {
1113 if( m_mandatoryFieldListIndexes[id] == row )
1114 {
1115 DisplayError( this, wxString::Format( _( "The first %d fields are mandatory." ),
1116 (int) m_mandatoryFieldListIndexes.size() ) );
1117 return false;
1118 }
1119 }
1120
1121 wxString fieldName = m_viewControlsDataModel->GetUntranslatedFieldName( row );
1122 int col = getDataModel()->GetFieldNameCol( fieldName );
1123
1124 if( col != -1 && getDataModel()->ColIsItemIdentifier( col ) )
1125 {
1126 DisplayError( this, wxString::Format( _( "The '%s' field cannot be removed." ),
1127 m_viewControlsDataModel->GetValue(
1128 row, DISPLAY_NAME_COLUMN ) ) );
1129 return false;
1130 }
1131
1132 return IsOK( this, wxString::Format( _( "Are you sure you want to remove the field '%s'?" ),
1133 m_viewControlsDataModel->GetValue( row, DISPLAY_NAME_COLUMN ) ) );
1134 },
1135 [&]( int row )
1136 {
1137 wxString fieldName = m_viewControlsDataModel->GetUntranslatedFieldName( row );
1138 int col = getDataModel()->GetFieldNameCol( fieldName );
1139
1140 if( col != -1 )
1141 getDataModel()->RemoveColumn( col );
1142
1143 m_viewControlsDataModel->DeleteRow( row );
1144
1146 OnModify();
1147 } );
1148}
1149
1150
1151void DIALOG_FIELDS_TABLE::OnRenameField( wxCommandEvent& aEvent )
1152{
1153 wxArrayInt selectedRows = m_viewControlsGrid->GetSelectedRows();
1154
1155 if( selectedRows.empty() && m_viewControlsGrid->GetGridCursorRow() >= 0 )
1156 selectedRows.push_back( m_viewControlsGrid->GetGridCursorRow() );
1157
1158 if( selectedRows.empty() )
1159 return;
1160
1161 int row = selectedRows[0];
1162
1163 for( FIELD_T id : MANDATORY_FIELDS )
1164 {
1165 if( m_mandatoryFieldListIndexes[id] == row )
1166 {
1167 DisplayError( this, wxString::Format( _( "The first %d fields are mandatory and names cannot be changed." ),
1168 (int) m_mandatoryFieldListIndexes.size() ) );
1169 return;
1170 }
1171 }
1172
1173 wxString fieldName = m_viewControlsDataModel->GetUntranslatedFieldName( row );
1174 wxString label = m_viewControlsDataModel->GetValue( row, LABEL_COLUMN );
1175 bool labelIsAutogenerated = label.IsSameAs( GetGeneratedFieldDisplayName( fieldName ) );
1176
1177 int col = getDataModel()->GetFieldNameCol( fieldName );
1178 wxCHECK_RET( col != -1, wxS( "Existing field name missing from data model" ) );
1179
1180 if( getDataModel()->ColIsItemIdentifier( col ) )
1181 {
1182 DisplayError( this, wxString::Format( _( "The '%s' field cannot be renamed." ),
1183 m_viewControlsDataModel->GetValue( row, DISPLAY_NAME_COLUMN ) ) );
1184 return;
1185 }
1186
1187 wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Rename Field" ), fieldName );
1188
1189 if( dlg.ShowModal() != wxID_OK )
1190 return;
1191
1192 wxString newFieldName = dlg.GetValue();
1193
1194 // No change, no-op
1195 if( newFieldName == fieldName )
1196 return;
1197
1198 if( newFieldName.IsEmpty() )
1199 {
1200 DisplayError( this, _( "Field must have a name." ) );
1201 return;
1202 }
1203
1204 // New field name already exists
1205 if( getDataModel()->GetFieldNameCol( newFieldName ) != -1 )
1206 {
1207 wxString confirm_msg = wxString::Format( _( "Field name %s already exists." ), newFieldName );
1208 DisplayError( this, confirm_msg );
1209 return;
1210 }
1211
1212 getDataModel()->RenameColumn( col, newFieldName );
1213 m_viewControlsDataModel->SetUntranslatedFieldName( row, newFieldName );
1214 m_viewControlsDataModel->SetValue( row, DISPLAY_NAME_COLUMN, newFieldName );
1215
1216 if( labelIsAutogenerated )
1217 {
1218 m_viewControlsDataModel->SetValue( row, LABEL_COLUMN, GetGeneratedFieldDisplayName( newFieldName ) );
1219 wxGridEvent evt( m_viewControlsGrid->GetId(), wxEVT_GRID_CELL_CHANGED, m_viewControlsGrid, row, LABEL_COLUMN );
1221 }
1222
1223 if( m_nbPages->GetSelection() == 1 )
1225 else
1226 {
1228 m_grid->ForceRefresh();
1229 }
1230
1232 OnModify();
1233}
1234
1235
1236void DIALOG_FIELDS_TABLE::OnFilterText( wxCommandEvent& aEvent )
1237{
1238 getDataModel()->SetFilter( m_filter->GetValue() );
1240 m_grid->ForceRefresh();
1241
1243}
1244
1245
1246void DIALOG_FIELDS_TABLE::OnFilterScope( wxCommandEvent& aEvent )
1247{
1248 getDataModel()->SetFilterScope( static_cast<BOM_FILTER_SCOPE>( m_filterScope->GetSelection() ) );
1250 m_grid->ForceRefresh();
1251
1253}
1254
1255
1257{
1261 m_grid->ForceRefresh();
1262
1264}
1265
1266
1267void DIALOG_FIELDS_TABLE::OnRegroupSymbols( wxCommandEvent& aEvent )
1268{
1270 m_grid->ForceRefresh();
1271}
1272
1273
1274void DIALOG_FIELDS_TABLE::OnColSort( wxGridEvent& aEvent )
1275{
1276 int sortCol = aEvent.GetCol();
1277 bool ascending;
1278
1279 // Don't sort by item number, it is generated by the sort
1280 if( getDataModel()->ColIsItemNumber( sortCol ) )
1281 {
1282 aEvent.Veto();
1283 return;
1284 }
1285
1286 // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the event, and
1287 // if we ask it will give us pre-event info.
1288 if( m_grid->IsSortingBy( sortCol ) )
1289 {
1290 // same column; invert ascending
1291 ascending = !m_grid->IsSortOrderAscending();
1292 }
1293 else
1294 {
1295 // different column; start with ascending
1296 ascending = true;
1297 }
1298
1299 getDataModel()->SetSorting( sortCol, ascending );
1301 m_grid->ForceRefresh();
1302
1304}
1305
1306
1307void DIALOG_FIELDS_TABLE::OnColMove( wxGridEvent& aEvent )
1308{
1309 int origPos = aEvent.GetCol();
1310
1311 // Save column widths since the setup function uses the saved config values
1313
1314 CallAfter(
1315 [origPos, this]()
1316 {
1317 int newPos = m_grid->GetColPos( origPos );
1318
1319#ifdef __WXMAC__
1320 if( newPos < origPos )
1321 newPos += 1;
1322#endif
1323
1324 getDataModel()->MoveColumn( origPos, newPos );
1325
1326 // "Unmove" the column since we've moved the column internally
1327 m_grid->ResetColPos();
1328
1329 // We need to reset all the column attr's to the correct column order
1331
1332 m_grid->ForceRefresh();
1334 } );
1335}
1336
1337
1338void DIALOG_FIELDS_TABLE::OnGridMouseMove( wxMouseEvent& aEvent )
1339{
1340 aEvent.Skip();
1341
1342 wxPoint pos = aEvent.GetPosition();
1343 int ux, uy;
1344 m_grid->CalcUnscrolledPosition( pos.x, pos.y, &ux, &uy );
1345 int row = m_grid->YToRow( uy );
1346 int col = m_grid->XToCol( ux );
1347
1348 if( row == wxNOT_FOUND || col == wxNOT_FOUND )
1349 {
1350 m_grid->GetGridWindow()->UnsetToolTip();
1351 return;
1352 }
1353
1354 wxString rawValue = getDataModel()->GetValue( row, col );
1355
1356 if( rawValue.Contains( wxT( "${" ) ) )
1357 m_grid->GetGridWindow()->SetToolTip( rawValue );
1358 else
1359 m_grid->GetGridWindow()->UnsetToolTip();
1360}
1361
1362
1363void DIALOG_FIELDS_TABLE::OnGridMouseWheel( wxMouseEvent& aEvent )
1364{
1365 if( aEvent.GetWheelAxis() != wxMOUSE_WHEEL_VERTICAL || aEvent.GetModifiers() != wxMOD_SHIFT )
1366 {
1367 aEvent.Skip();
1368 return;
1369 }
1370
1371 // Accumulate so high-resolution wheels and trackpads aren't truncated.
1372 m_gridWheelRotation += aEvent.GetWheelRotation();
1373
1374 int lines = m_gridWheelRotation / aEvent.GetWheelDelta();
1375 m_gridWheelRotation -= lines * aEvent.GetWheelDelta();
1376
1377 if( lines == 0 )
1378 return;
1379
1380 int scrollLines = aEvent.IsPageScroll() ? m_grid->GetScrollPageSize( wxHORIZONTAL )
1381 : aEvent.GetLinesPerAction();
1382
1383 wxPoint viewStart = m_grid->GetViewStart();
1384 m_grid->Scroll( viewStart.x - lines * scrollLines, viewStart.y );
1385}
1386
1387
1388void DIALOG_FIELDS_TABLE::OnPageChanged( wxNotebookEvent& aEvent )
1389{
1390 if( getDataModel()->GetColsCount() )
1392}
1393
1394
1395void DIALOG_FIELDS_TABLE::OnPreviewRefresh( wxCommandEvent& aEvent )
1396{
1399}
1400
1401
1403{
1404 // Build the absolute path of current output directory to preselect it in the file browser.
1405 wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() );
1406 path = Prj().AbsolutePath( path );
1407
1408 // Calculate the export filename
1409 wxFileName fn( Prj().AbsolutePath( m_parentFrame->GetCurrentFileName() ) );
1410 fn.SetExt( FILEEXT::CsvFileExtension );
1411
1412 wxFileDialog saveDlg( this, _( "Bill of Materials Output File" ), path, fn.GetFullName(),
1413 FILEEXT::CsvFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
1414
1416
1417 if( saveDlg.ShowModal() == wxID_CANCEL )
1418 return;
1419
1420 wxFileName file = wxFileName( saveDlg.GetPath() );
1421 wxString defaultPath = fn.GetPathWithSep();
1422
1423 if( IsOK( this, wxString::Format( _( "Do you want to use a path relative to\n'%s'?" ), defaultPath ) ) )
1424 {
1425 if( !file.MakeRelativeTo( defaultPath ) )
1426 {
1427 DisplayErrorMessage( this, _( "Cannot make path relative (target volume different from file "
1428 "volume)!" ) );
1429 }
1430 }
1431
1432 m_outputFileName->SetValue( file.GetFullPath() );
1433}
1434
1435
1436void DIALOG_FIELDS_TABLE::OnExport( wxCommandEvent& aEvent )
1437{
1438 if( getDataModel()->IsEdited() )
1439 {
1440 if( OKOrCancelDialog( nullptr, _( "Unsaved data" ),
1441 _( "Changes have not yet been saved. Export unsaved data?" ), "", _( "OK" ),
1442 _( "Cancel" ) )
1443 == wxID_CANCEL )
1444 {
1445 return;
1446 }
1447 }
1448
1449 // Create output directory if it does not exist (also transform it in absolute form).
1450 // Bail if it fails.
1451
1452 std::function<bool( wxString* )> textResolver =
1453 [&]( wxString* token ) -> bool
1454 {
1455 return resolveTextVar( token );
1456 };
1457
1458 wxString sourceFileName = m_parentFrame->GetCurrentFileName();
1459 wxString path = m_outputFileName->GetValue();
1460
1461 if( path.IsEmpty() )
1462 {
1463 // Match the behaviour of other exporters and default to the source filename with a CSV
1464 // extension in the project directory when the user leaves the field blank.
1465 path = GetDefaultBomFileName( sourceFileName );
1466
1467 if( path.IsEmpty() )
1468 {
1469 DisplayError( this, _( "No output file specified in Export tab." ) );
1470 return;
1471 }
1472
1473 m_outputFileName->SetValue( path );
1474 }
1475
1478
1479 wxFileName outputFile = wxFileName::FileName( path );
1480 wxString msg;
1481
1482 if( !EnsureFileDirectoryExists( &outputFile, Prj().AbsolutePath( sourceFileName ), &NULL_REPORTER::GetInstance() ) )
1483 {
1484 msg.Printf( _( "Could not open/create path '%s'." ), outputFile.GetPath() );
1485 DisplayError( this, msg );
1486 return;
1487 }
1488
1489 wxFFile out( outputFile.GetFullPath(), "wb" );
1490
1491 if( !out.IsOpened() )
1492 {
1493 msg.Printf( _( "Could not create BOM output '%s'." ), outputFile.GetFullPath() );
1494 DisplayError( this, msg );
1495 return;
1496 }
1497
1499
1500 if( !out.Write( m_textOutput->GetValue() ) )
1501 {
1502 msg.Printf( _( "Could not write BOM output '%s'." ), outputFile.GetFullPath() );
1503 DisplayError( this, msg );
1504 return;
1505 }
1506
1507 // close the file before we tell the user it's done with the info modal :workflow meme:
1508 out.Close();
1509
1510 if( m_cfgBomSettings.m_BomExportFileName != m_outputFileName->GetValue() )
1511 {
1512 m_cfgBomSettings.m_BomExportFileName = m_outputFileName->GetValue();
1514 }
1515
1516 msg.Printf( _( "Wrote BOM output to '%s'" ), outputFile.GetFullPath() );
1517 DisplayInfoMessage( this, msg );
1518}
1519
1520
1522{
1523 bool saveIncludeExcudedFromBOM = getDataModel()->GetIncludeExcludedFromBOM();
1524
1527
1528 m_textOutput->SetValue( getDataModel()->Export( GetCurrentBomFmtSettings() ) );
1529
1530 if( saveIncludeExcudedFromBOM )
1531 {
1534 }
1535}
1536
1537
1539{
1540 wxString retv;
1541
1542 int selection = m_variantListBox->GetSelection();
1543
1544 if( ( selection == wxNOT_FOUND ) || ( m_variantListBox->GetString( selection ) == GetDefaultVariantName() ) )
1545 return retv;
1546
1547 return m_variantListBox->GetString( selection );
1548}
1549
1550
1551std::vector<BOM_PRESET> DIALOG_FIELDS_TABLE::GetUserBomPresets() const
1552{
1553 std::vector<BOM_PRESET> ret;
1554
1555 for( const std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
1556 {
1557 if( !pair.second.readOnly )
1558 ret.emplace_back( pair.second );
1559 }
1560
1561 return ret;
1562}
1563
1564
1565void DIALOG_FIELDS_TABLE::SetUserBomPresets( std::vector<BOM_PRESET>& aPresetList )
1566{
1567 // Reset to defaults
1569
1570 for( const BOM_PRESET& preset : aPresetList )
1571 {
1572 if( m_bomPresets.count( preset.name ) )
1573 continue;
1574
1575 m_bomPresets[preset.name] = preset;
1576
1577 m_bomPresetMRU.Add( preset.name );
1578 }
1579
1581}
1582
1583
1584void DIALOG_FIELDS_TABLE::ApplyBomPreset( const wxString& aPresetName )
1585{
1586 updateBomPresetSelection( aPresetName );
1587
1588 wxCommandEvent dummy;
1590}
1591
1592
1593void DIALOG_FIELDS_TABLE::ApplyBomPreset( const BOM_PRESET& aPreset )
1594{
1595 BOM_PRESET preset = aPreset;
1596
1597 if( preset.fieldsOrdered.empty() )
1598 {
1599 for( const BOM_PRESET& builtIn : getBuiltInBomPresets() )
1600 {
1601 if( builtIn.name == BOM_PRESET::DefaultEditing().name )
1602 {
1603 preset = builtIn;
1604 break;
1605 }
1606 }
1607 }
1608
1609 if( m_bomPresets.count( preset.name ) )
1610 m_currentBomPreset = &m_bomPresets[preset.name];
1611 else
1612 m_currentBomPreset = nullptr;
1613
1614 if( m_currentBomPreset && !m_currentBomPreset->readOnly )
1616 else
1617 m_lastSelectedBomPreset = nullptr;
1618
1619 updateBomPresetSelection( preset.name );
1620 doApplyBomPreset( preset );
1621}
1622
1623
1624void DIALOG_FIELDS_TABLE::doApplyBomPreset( const BOM_PRESET& aPreset )
1625{
1626 // Disable rebuilds while we're applying the preset otherwise we'll be
1627 // rebuilding the model constantly while firing off wx events
1629
1630 // Basically, we apply the BOM preset to the data model and then
1631 // update our UI to reflect resulting the data model state, not the preset.
1633 getDataModel()->ApplyBomPreset( aPreset );
1634
1635 // BOM Presets can add, but not remove, columns, so make sure the view controls
1636 // grid has all of them before starting
1637 for( int i = 0; i < getDataModel()->GetColsCount(); i++ )
1638 {
1639 const wxString& fieldName( getDataModel()->GetColFieldName( i ) );
1640 bool found = false;
1641
1642 for( int j = 0; j < m_viewControlsDataModel->GetNumberRows(); j++ )
1643 {
1644 if( m_viewControlsDataModel->GetUntranslatedFieldName( j ) == fieldName )
1645 {
1646 found = true;
1647 break;
1648 }
1649 }
1650
1651 // Properties like label, etc. will be added in the next loop
1652 if( !found )
1653 AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), false, false );
1654 }
1655
1656 // Sync all fields
1657 for( int i = 0; i < m_viewControlsDataModel->GetNumberRows(); i++ )
1658 {
1659 const wxString& fieldName( m_viewControlsDataModel->GetUntranslatedFieldName( i ) );
1660 int col = getDataModel()->GetFieldNameCol( fieldName );
1661
1662 if( col == -1 )
1663 {
1664 wxASSERT_MSG( true, "Fields control has a field not found in the data model." );
1665 continue;
1666 }
1667
1668 std::string fieldNameStr( fieldName.ToUTF8() );
1669
1670 // Set column labels
1671 const wxString& label = getDataModel()->GetColLabelValue( col );
1672 m_viewControlsDataModel->SetValue( i, LABEL_COLUMN, label );
1673 m_grid->SetColLabelValue( col, label );
1674
1675 if( m_cfgDialogSettings.field_widths.count( fieldNameStr ) )
1676 m_grid->SetColSize( col, m_cfgDialogSettings.field_widths.at( fieldNameStr ) );
1677
1678 // Set shown columns
1679 bool show = getDataModel()->GetShowColumn( col );
1680 m_viewControlsDataModel->SetValueAsBool( i, SHOW_FIELD_COLUMN, show );
1681
1682 if( show )
1683 m_grid->ShowCol( col );
1684 else
1685 m_grid->HideCol( col );
1686
1687 // Set grouped columns
1688 bool groupBy = getDataModel()->GetGroupColumn( col );
1689 m_viewControlsDataModel->SetValueAsBool( i, GROUP_BY_COLUMN, groupBy );
1690 }
1691
1692 m_grid->SetSortingColumn( getDataModel()->GetSortCol(), getDataModel()->GetSortAsc() );
1693 m_groupSymbolsBox->SetValue( getDataModel()->GetGroupingEnabled() );
1694 m_filter->ChangeValue( getDataModel()->GetFilter() );
1695 m_filterScope->SetSelection( static_cast<int>( getDataModel()->GetFilterScope() ) );
1696
1697 // Rebuild before sizing columns so their widths account for the current row data.
1700
1702
1703 if( m_nbPages->GetSelection() == 1 )
1705 else
1706 m_grid->ForceRefresh();
1707}
1708
1709
1711{
1712 m_bomPresets.clear();
1713 m_bomPresetMRU.clear();
1714
1715 // Load the read-only defaults
1716 for( const BOM_PRESET& preset : getBuiltInBomPresets() )
1717 {
1718 m_bomPresets[preset.name] = preset;
1719 m_bomPresets[preset.name].readOnly = true;
1720
1721 m_bomPresetMRU.Add( preset.name );
1722 }
1723}
1724
1725
1726std::vector<BOM_PRESET> DIALOG_FIELDS_TABLE::getBuiltInBomPresets() const
1727{
1728 return BOM_PRESET::BuiltInPresets();
1729}
1730
1731
1733{
1734 m_cbBomPresets->Clear();
1735
1736 int idx = 0;
1737 int default_idx = 0;
1738
1739 for( const auto& [presetName, preset] : m_bomPresets )
1740 {
1741 wxString label = preset.readOnly ? wxGetTranslation( presetName ) : presetName;
1742 m_cbBomPresets->Append( label, (void*) &preset );
1743
1744 if( presetName == BOM_PRESET::DefaultEditing().name )
1745 default_idx = idx;
1746
1747 idx++;
1748 }
1749
1750 m_cbBomPresets->Append( wxT( "---" ) );
1751
1752 if( !m_job )
1753 {
1754 m_cbBomPresets->Append( _( "Save preset..." ) );
1755 m_cbBomPresets->Append( _( "Delete preset..." ) );
1756 }
1757
1758 // At least the built-in presets should always be present
1759 wxASSERT( !m_bomPresets.empty() );
1760
1761 m_cbBomPresets->SetSelection( default_idx );
1762 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( default_idx ) );
1763}
1764
1765
1767{
1768 return aPresetCount - ( m_job ? 1 : 3 );
1769}
1770
1771
1773{
1774 return m_job ? wxNOT_FOUND : aPresetCount - 2;
1775}
1776
1777
1779{
1780 return m_job ? wxNOT_FOUND : aPresetCount - 1;
1781}
1782
1783
1788
1789
1791{
1792 BOM_PRESET current = getDataModelBomPreset();
1793
1794 auto it = std::find_if( m_bomPresets.begin(), m_bomPresets.end(),
1795 [&]( const std::pair<const wxString, BOM_PRESET>& aPair )
1796 {
1797 const BOM_PRESET& preset = aPair.second;
1798
1799 // Check the simple settings first
1800 if( !( preset.sortAsc == current.sortAsc
1801 && preset.filterString == current.filterString
1802 && preset.filterScope == current.filterScope
1803 && preset.groupSymbols == current.groupSymbols
1804 && preset.excludeDNP == current.excludeDNP
1805 && preset.includeExcludedFromBOM == current.includeExcludedFromBOM ) )
1806 {
1807 return false;
1808 }
1809
1810 if( preset.sortField != current.sortField )
1811 return false;
1812
1813 // Only compare shown or grouped fields
1814 std::vector<BOM_FIELD> A, B;
1815
1816 for( const BOM_FIELD& field : preset.fieldsOrdered )
1817 {
1818 if( field.show || field.groupBy )
1819 A.emplace_back( field );
1820 }
1821
1822 for( const BOM_FIELD& field : current.fieldsOrdered )
1823 {
1824 if( field.show || field.groupBy )
1825 B.emplace_back( field );
1826 }
1827
1828 return A == B;
1829 } );
1830
1831 if( it != m_bomPresets.end() )
1832 {
1833 m_cbBomPresets->SetSelection( findPresetIndexByPointer( m_cbBomPresets, &it->second ) );
1834 }
1835 else
1836 {
1837 m_cbBomPresets->SetSelection( presetDashDashDashIndex( m_cbBomPresets->GetCount() ) );
1838 }
1839
1840 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( m_cbBomPresets->GetSelection() ) );
1841}
1842
1843
1845{
1846 int idx = findPresetIndexByName( m_cbBomPresets, m_bomPresets, aName );
1847
1848 if( idx >= 0 && m_cbBomPresets->GetSelection() != idx )
1849 {
1850 m_cbBomPresets->SetSelection( idx );
1851 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( idx ) );
1852 }
1853 else if( idx < 0 )
1854 {
1855 m_cbBomPresets->SetSelection( presetDashDashDashIndex( m_cbBomPresets->GetCount() ) );
1856 }
1857}
1858
1859
1860void DIALOG_FIELDS_TABLE::onBomPresetChanged( wxCommandEvent& aEvent )
1861{
1862 int count = m_cbBomPresets->GetCount();
1863 int index = m_cbBomPresets->GetSelection();
1864
1865 auto resetSelection =
1866 [&]()
1867 {
1868 int presetIndex = findPresetIndexByPointer( m_cbBomPresets, m_currentBomPreset );
1869
1870 if( presetIndex == wxNOT_FOUND )
1871 m_cbBomPresets->SetSelection( presetDashDashDashIndex( m_cbBomPresets->GetCount() ) );
1872 else
1873 m_cbBomPresets->SetSelection( presetIndex );
1874 };
1875
1876 if( index == presetDashDashDashIndex( count ) )
1877 {
1878 // Separator: reject the selection
1879 resetSelection();
1880 return;
1881 }
1882 else if( index == presetSavePresetIndex( count ) )
1883 {
1884 // Save current state to new preset
1885 wxString name;
1886
1889
1890 wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
1891
1892 if( dlg.ShowModal() != wxID_OK )
1893 {
1894 resetSelection();
1895 return;
1896 }
1897
1898 name = dlg.GetValue();
1899 bool exists = m_bomPresets.count( name );
1900
1901 if( !exists )
1902 {
1904 m_bomPresets[name].readOnly = false;
1905 m_bomPresets[name].name = name;
1906 }
1907
1908 BOM_PRESET* preset = &m_bomPresets[name];
1909
1910 if( !exists )
1911 {
1912 index = m_cbBomPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
1913 }
1914 else if( preset->readOnly )
1915 {
1916 wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
1917 _( "Error" ), wxOK | wxICON_ERROR, this );
1918 resetSelection();
1919 return;
1920 }
1921 else
1922 {
1923 // Ask the user if they want to overwrite the existing preset
1924 if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
1925 {
1926 resetSelection();
1927 return;
1928 }
1929
1930 *preset = getDataModelBomPreset();
1931 preset->name = name;
1932
1933 index = findPresetIndexByPointer( m_cbBomPresets, preset );
1934
1935 if( m_bomPresetMRU.Index( name ) != wxNOT_FOUND )
1936 m_bomPresetMRU.Remove( name );
1937 }
1938
1939 m_currentBomPreset = preset;
1940 m_cbBomPresets->SetSelection( index );
1941 m_bomPresetMRU.Insert( name, 0 );
1942
1943 return;
1944 }
1945 else if( index == presetDeletePresetIndex( count ) )
1946 {
1947 // Delete a preset
1948 wxArrayString headers;
1949 std::vector<wxArrayString> items;
1950
1951 headers.Add( _( "Presets" ) );
1952
1953 for( const auto& [name, preset] : m_bomPresets )
1954 {
1955 if( !preset.readOnly )
1956 {
1957 wxArrayString item;
1958 item.Add( name );
1959 items.emplace_back( item );
1960 }
1961 }
1962
1963 EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
1964 dlg.SetListLabel( _( "Select preset:" ) );
1965
1966 if( dlg.ShowModal() == wxID_OK )
1967 {
1968 wxString presetName = dlg.GetTextSelection();
1969 auto presetIt = m_bomPresets.find( presetName );
1970 int idx = findPresetIndexByName( m_cbBomPresets, m_bomPresets, presetName );
1971
1972 if( idx != wxNOT_FOUND && presetIt != m_bomPresets.end() )
1973 {
1974 if( m_lastSelectedBomPreset == &presetIt->second )
1975 m_lastSelectedBomPreset = nullptr;
1976
1977 m_currentBomPreset = nullptr;
1978 m_cbBomPresets->Delete( idx );
1979 m_bomPresets.erase( presetIt );
1980 }
1981
1982 if( m_bomPresetMRU.Index( presetName ) != wxNOT_FOUND )
1983 m_bomPresetMRU.Remove( presetName );
1984 }
1985
1986 resetSelection();
1987 return;
1988 }
1989
1990 BOM_PRESET* preset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( index ) );
1991 m_currentBomPreset = preset;
1992
1993 m_lastSelectedBomPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
1994
1995 if( preset )
1996 {
1997 doApplyBomPreset( *preset );
1999 m_currentBomPreset = preset;
2000
2001 if( !m_currentBomPreset->name.IsEmpty() )
2002 {
2003 if( m_bomPresetMRU.Index( preset->name ) != wxNOT_FOUND )
2004 m_bomPresetMRU.Remove( preset->name );
2005
2006 m_bomPresetMRU.Insert( preset->name, 0 );
2007 }
2008 }
2009}
2010
2011
2013{
2014 BOM_FMT_PRESET current;
2015
2016 int selection = m_cbBomFmtPresets->GetSelection();
2017
2018 if( selection != wxNOT_FOUND )
2019 {
2020 if( auto* preset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( selection ) ) )
2021 current.name = preset->name;
2022 else
2023 current.name = m_cbBomFmtPresets->GetString( selection );
2024 }
2025
2026 current.fieldDelimiter = m_textFieldDelimiter->GetValue();
2027 current.stringDelimiter = m_textStringDelimiter->GetValue();
2028 current.refDelimiter = m_textRefDelimiter->GetValue();
2029 current.refRangeDelimiter = m_textRefRangeDelimiter->GetValue();
2030 current.keepTabs = m_checkKeepTabs->GetValue();
2031 current.keepLineBreaks = m_checkKeepLineBreaks->GetValue();
2033
2034 return current;
2035}
2036
2037
2038std::vector<BOM_FMT_PRESET> DIALOG_FIELDS_TABLE::GetUserBomFmtPresets() const
2039{
2040 std::vector<BOM_FMT_PRESET> ret;
2041
2042 for( const auto& [name, preset] : m_bomFmtPresets )
2043 {
2044 if( !preset.readOnly )
2045 ret.emplace_back( preset );
2046 }
2047
2048 return ret;
2049}
2050
2051
2052void DIALOG_FIELDS_TABLE::SetUserBomFmtPresets( std::vector<BOM_FMT_PRESET>& aPresetList )
2053{
2054 // Reset to defaults
2056
2057 for( const BOM_FMT_PRESET& preset : aPresetList )
2058 {
2059 if( m_bomFmtPresets.count( preset.name ) )
2060 continue;
2061
2062 m_bomFmtPresets[preset.name] = preset;
2063
2064 m_bomFmtPresetMRU.Add( preset.name );
2065 }
2066
2068}
2069
2070
2071void DIALOG_FIELDS_TABLE::ApplyBomFmtPreset( const wxString& aPresetName )
2072{
2073 updateBomFmtPresetSelection( aPresetName );
2074
2075 wxCommandEvent dummy;
2077}
2078
2079
2081{
2082 m_currentBomFmtPreset = nullptr;
2084
2085 if( m_bomFmtPresets.count( aPreset.name ) )
2087
2090
2092 doApplyBomFmtPreset( aPreset );
2093}
2094
2095
2097{
2098 m_textFieldDelimiter->ChangeValue( aPreset.fieldDelimiter );
2099 m_textStringDelimiter->ChangeValue( aPreset.stringDelimiter );
2100 m_textRefDelimiter->ChangeValue( aPreset.refDelimiter );
2101 m_textRefRangeDelimiter->ChangeValue( aPreset.refRangeDelimiter );
2102 m_checkKeepTabs->SetValue( aPreset.keepTabs );
2103 m_checkKeepLineBreaks->SetValue( aPreset.keepLineBreaks );
2105
2106 // Refresh the preview if that's the current page
2107 if( m_nbPages->GetSelection() == 1 )
2109}
2110
2111
2113{
2114 m_bomFmtPresets.clear();
2115 m_bomFmtPresetMRU.clear();
2116
2117 // Load the read-only defaults
2118 for( const BOM_FMT_PRESET& preset : BOM_FMT_PRESET::BuiltInPresets() )
2119 {
2120 m_bomFmtPresets[preset.name] = preset;
2121 m_bomFmtPresets[preset.name].readOnly = true;
2122
2123 m_bomFmtPresetMRU.Add( preset.name );
2124 }
2125}
2126
2127
2129{
2130 m_cbBomFmtPresets->Clear();
2131
2132 int idx = 0;
2133 int default_idx = 0;
2134
2135 for( const auto& [presetName, preset] : m_bomFmtPresets )
2136 {
2137 wxString label = preset.readOnly ? wxGetTranslation( presetName ) : presetName;
2138 m_cbBomFmtPresets->Append( label, (void*) &preset );
2139
2140 if( presetName == BOM_FMT_PRESET::CSV().name )
2141 default_idx = idx;
2142
2143 idx++;
2144 }
2145
2146 m_cbBomFmtPresets->Append( wxT( "---" ) );
2147
2148 if( !m_job )
2149 {
2150 m_cbBomFmtPresets->Append( _( "Save preset..." ) );
2151 m_cbBomFmtPresets->Append( _( "Delete preset..." ) );
2152 }
2153
2154 // At least the built-in presets should always be present
2155 wxASSERT( !m_bomFmtPresets.empty() );
2156
2157 m_cbBomFmtPresets->SetSelection( default_idx );
2158 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( default_idx ) );
2159}
2160
2161
2163{
2165
2166 auto it = std::find_if( m_bomFmtPresets.begin(), m_bomFmtPresets.end(),
2167 [&]( const std::pair<const wxString, BOM_FMT_PRESET>& aPair )
2168 {
2169 return ( aPair.second.fieldDelimiter == current.fieldDelimiter
2170 && aPair.second.stringDelimiter == current.stringDelimiter
2171 && aPair.second.refDelimiter == current.refDelimiter
2172 && aPair.second.refRangeDelimiter == current.refRangeDelimiter
2173 && aPair.second.keepTabs == current.keepTabs
2174 && aPair.second.keepLineBreaks == current.keepLineBreaks
2175 && aPair.second.includeByteOrderMark == current.includeByteOrderMark );
2176 } );
2177
2178 if( it != m_bomFmtPresets.end() )
2179 {
2180 m_cbBomFmtPresets->SetSelection( findPresetIndexByPointer( m_cbBomFmtPresets, &it->second ) );
2181 }
2182 else
2183 {
2184 m_cbBomFmtPresets->SetSelection( presetDashDashDashIndex( m_cbBomFmtPresets->GetCount() ) );
2185 }
2186
2187 int idx = m_cbBomFmtPresets->GetSelection();
2188 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
2189}
2190
2191
2193{
2194 int idx = findPresetIndexByName( m_cbBomFmtPresets, m_bomFmtPresets, aName );
2195
2196 if( idx >= 0 && m_cbBomFmtPresets->GetSelection() != idx )
2197 {
2198 m_cbBomFmtPresets->SetSelection( idx );
2199 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
2200 }
2201 else if( idx < 0 )
2202 {
2203 m_cbBomFmtPresets->SetSelection( presetDashDashDashIndex( m_cbBomFmtPresets->GetCount() ) );
2204 }
2205}
2206
2207
2209{
2210 int count = m_cbBomFmtPresets->GetCount();
2211 int index = m_cbBomFmtPresets->GetSelection();
2212
2213 auto resetSelection =
2214 [&]()
2215 {
2216 int presetIndex = findPresetIndexByPointer( m_cbBomFmtPresets, m_currentBomFmtPreset );
2217
2218 if( presetIndex == wxNOT_FOUND )
2219 m_cbBomFmtPresets->SetSelection( presetDashDashDashIndex( m_cbBomFmtPresets->GetCount() ) );
2220 else
2221 m_cbBomFmtPresets->SetSelection( presetIndex );
2222 };
2223
2224 if( index == presetDashDashDashIndex( count ) )
2225 {
2226 // Separator: reject the selection
2227 resetSelection();
2228 return;
2229 }
2230 else if( index == presetSavePresetIndex( count ) )
2231 {
2232 // Save current state to new preset
2233 wxString name;
2234
2237
2238 wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
2239
2240 if( dlg.ShowModal() != wxID_OK )
2241 {
2242 resetSelection();
2243 return;
2244 }
2245
2246 name = dlg.GetValue();
2247 bool exists = m_bomFmtPresets.count( name );
2248
2249 if( !exists )
2250 {
2252 m_bomFmtPresets[name].readOnly = false;
2253 m_bomFmtPresets[name].name = name;
2254 }
2255
2257
2258 if( !exists )
2259 {
2260 index = m_cbBomFmtPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
2261 }
2262 else if( preset->readOnly )
2263 {
2264 wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
2265 _( "Error" ), wxOK | wxICON_ERROR, this );
2266 resetSelection();
2267 return;
2268 }
2269 else
2270 {
2271 // Ask the user if they want to overwrite the existing preset
2272 if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
2273 {
2274 resetSelection();
2275 return;
2276 }
2277
2278 *preset = GetCurrentBomFmtSettings();
2279 preset->name = name;
2280
2281 index = findPresetIndexByPointer( m_cbBomFmtPresets, preset );
2282
2283 if( m_bomFmtPresetMRU.Index( name ) != wxNOT_FOUND )
2284 m_bomFmtPresetMRU.Remove( name );
2285 }
2286
2287 m_currentBomFmtPreset = preset;
2288 m_cbBomFmtPresets->SetSelection( index );
2289 m_bomFmtPresetMRU.Insert( name, 0 );
2290
2291 return;
2292 }
2293 else if( index == presetDeletePresetIndex( count ) )
2294 {
2295 // Delete a preset
2296 wxArrayString headers;
2297 std::vector<wxArrayString> items;
2298
2299 headers.Add( _( "Presets" ) );
2300
2301 for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
2302 {
2303 if( !pair.second.readOnly )
2304 {
2305 wxArrayString item;
2306 item.Add( pair.first );
2307 items.emplace_back( item );
2308 }
2309 }
2310
2311 EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
2312 dlg.SetListLabel( _( "Select preset:" ) );
2313
2314 if( dlg.ShowModal() == wxID_OK )
2315 {
2316 wxString presetName = dlg.GetTextSelection();
2317 auto presetIt = m_bomFmtPresets.find( presetName );
2318 int idx = findPresetIndexByName( m_cbBomFmtPresets, m_bomFmtPresets, presetName );
2319
2320 if( idx != wxNOT_FOUND && presetIt != m_bomFmtPresets.end() )
2321 {
2322 if( m_lastSelectedBomFmtPreset == &presetIt->second )
2324
2325 m_currentBomFmtPreset = nullptr;
2326 m_cbBomFmtPresets->Delete( idx );
2327 m_bomFmtPresets.erase( presetIt );
2328 }
2329
2330 if( m_bomFmtPresetMRU.Index( presetName ) != wxNOT_FOUND )
2331 m_bomFmtPresetMRU.Remove( presetName );
2332 }
2333
2334 resetSelection();
2335 return;
2336 }
2337
2338 auto* preset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( index ) );
2339 m_currentBomFmtPreset = preset;
2340
2341 m_lastSelectedBomFmtPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
2342
2343 if( preset )
2344 {
2345 doApplyBomFmtPreset( *preset );
2347 m_currentBomFmtPreset = preset;
2348
2349 if( !m_currentBomFmtPreset->name.IsEmpty() )
2350 {
2351 if( m_bomFmtPresetMRU.Index( preset->name ) != wxNOT_FOUND )
2352 m_bomFmtPresetMRU.Remove( preset->name );
2353
2354 m_bomFmtPresetMRU.Insert( preset->name, 0 );
2355 }
2356 }
2357}
int index
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
BOM_FILTER_SCOPE
STD_BITMAP_BUTTON * m_addVariantButton
STD_BITMAP_BUTTON * m_renameVariantButton
DIALOG_FIELDS_TABLE_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Fields Table"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxMAXIMIZE_BOX|wxRESIZE_BORDER)
STD_BITMAP_BUTTON * m_removeFieldButton
STD_BITMAP_BUTTON * m_renameFieldButton
wxSplitterWindow * m_splitterMainWindow
STD_BITMAP_BUTTON * m_copyVariantButton
STD_BITMAP_BUTTON * m_deleteVariantButton
STD_BITMAP_BUTTON * m_editVariantDescButton
std::map< wxString, BOM_PRESET > m_bomPresets
BOM_PRESET * m_lastSelectedBomPreset
void saveJobSettings(JOB_EXPORT_BOM &aJob)
virtual wxGridCellEditor * createReferenceEditor()
void SetUserBomFmtPresets(std::vector< BOM_FMT_PRESET > &aPresetList)
wxSize GetDefaultDialogSize() const
virtual wxGridCellEditor * createDatasheetEditor()=0
virtual bool resolveTextVar(wxString *aToken) const =0
BOM_FMT_PRESET * m_lastSelectedBomFmtPreset
void OnColMove(wxGridEvent &aEvent)
int presetDashDashDashIndex(int aPresetCount) const
void OnColSort(wxGridEvent &aEvent)
void OnGridMouseMove(wxMouseEvent &aEvent)
bool savePresets(bool aSaveCurrentSettings)
wxArrayString m_bomFmtPresetMRU
void OnSizeViewControlsGrid(wxSizeEvent &event) override
void OnViewControlsCellChanged(wxGridEvent &aEvent) override
BOM_FMT_PRESET * m_currentBomFmtPreset
int presetDeletePresetIndex(int aPresetCount) const
void OnTableRangeSelectionChanged(wxGridRangeSelectEvent &aEvent)
wxString getSelectedVariant() const
void doApplyBomFmtPreset(const BOM_FMT_PRESET &aPreset)
void OnFilterText(wxCommandEvent &aEvent) override
void doApplyBomPreset(const BOM_PRESET &aPreset)
void OnPageChanged(wxNotebookEvent &aEvent) override
void ShowHideColumn(int aCol, bool aShow)
void OnExport(wxCommandEvent &aEvent) override
virtual void OnTableSelectionChanged(const std::set< int > &aRows)=0
void updateBomPresetSelection(const wxString &aName)
void OnFilterScope(wxCommandEvent &aEvent) override
BOM_FMT_PRESET GetCurrentBomFmtSettings()
Returns a formatting configuration corresponding to the values in the UI controls of the dialog.
void RestoreGridSelection(const std::set< KIID_PATH > &aItemKeys)
Restore a selection previously returned by SaveGridSelection().
std::map< FIELD_T, int > m_mandatoryFieldListIndexes
virtual wxGridCellEditor * createFootprintEditor()
void onBomFmtPresetChanged(wxCommandEvent &aEvent)
int presetSavePresetIndex(int aPresetCount) const
void OnRenameField(wxCommandEvent &aEvent) override
void OnGroupSymbolsToggled(wxCommandEvent &aEvent) override
void OnPreviewRefresh(wxCommandEvent &aEvent) override
virtual std::vector< BOM_PRESET > getBuiltInBomPresets() const
void updateBomFmtPresetSelection(const wxString &aName)
void OnTableColSize(wxGridSizeEvent &event) override
void SetUserBomPresets(std::vector< BOM_PRESET > &aPresetList)
void ApplyBomFmtPreset(const wxString &aPresetName)
DIALOG_FIELDS_TABLE(wxWindow *aParent, FIELDS_TABLE_SETTINGS &aPanelSettings, FIELDS_TABLE_BOM_SETTINGS &aBomSettings, JOB_EXPORT_BOM *aJob)
void SetupColumnProperties(int aCol)
FIELDS_TABLE_BOM_SETTINGS & m_cfgBomSettings
static void loadJobBomFmtPreset(const JOB_EXPORT_BOM &aJob, BOM_FMT_PRESET &aPreset)
FIELDS_TABLE_SETTINGS & m_cfgDialogSettings
std::map< wxString, BOM_FMT_PRESET > m_bomFmtPresets
static void loadJobBomPreset(const JOB_EXPORT_BOM &aJob, BOM_PRESET &aPreset)
virtual wxString resolveVariant() const =0
std::set< int > GetSelectedGridRows() const
Return the explicitly selected grid rows, or the cursor row if the selection is empty.
void OnGridMouseWheel(wxMouseEvent &aEvent)
void OnSidebarToggle(wxCommandEvent &event) override
void OnAddField(wxCommandEvent &aEvent) override
std::vector< BOM_PRESET > GetUserBomPresets() const
void onBomPresetChanged(wxCommandEvent &aEvent)
virtual void onBomSettingsChanged()
void SetReadOnly(bool aReadOnly)
std::set< KIID_PATH > SaveGridSelection()
Save the grid selection by stable item identity so it can survive a row rebuild.
void ApplyBomPreset(const wxString &aPresetName)
void OnFilterMouseMoved(wxMouseEvent &event) override
void OnRegroupSymbols(wxCommandEvent &aEvent) override
void OnOutputFileBrowseClicked(wxCommandEvent &aEvent) override
void OnTableValueChanged(wxGridEvent &event) override
virtual FIELDS_TABLE_DATA_MODEL_BASE * getDataModel() const =0
VIEW_CONTROLS_GRID_DATA_MODEL * m_viewControlsDataModel
static wxString GetDefaultBomFileName(const wxString &aInputFileName)
Derive the default BOM output file name from the input file name by swapping the extension to CSV.
std::vector< BOM_FMT_PRESET > GetUserBomFmtPresets() const
void OnTableLabelClick(wxGridEvent &event) override
void setSideBarButtonLook(bool aIsLeftPanelCollapsed)
void AddField(const wxString &aFieldName, const wxString &aLabelValue, bool aShow, bool aGroupBy, bool aAddedByUser=false)
void OnRemoveField(wxCommandEvent &aEvent) override
void OptOut(wxWindow *aWindow)
Opt out of control state saving.
EDA_BASE_FRAME * m_parentFrame
int ShowModal() override
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)
Contains everything completly generic to fields tables data models, as well as column functionality t...
virtual void SetCurrentVariant(const wxString &aVariantName)=0
Select the displayed variant, retaining un-applied edits in every variant.
void SetSorting(int aCol, bool aAscending)
void MoveColumn(int aCol, int aNewPos)
void SetShowColumn(int aCol, bool aShow)
void SetIncludeExcludedFromBOM(bool aInclude)
virtual void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser)=0
void SetColLabelValue(int aCol, const wxString &aLabel) override
void SetFilterScope(BOM_FILTER_SCOPE aScope)
int GetFieldNameCol(const wxString &aFieldName) const
virtual void ExpandCollapseRow(int aRow)=0
void RenameColumn(int aCol, const wxString &newName)
void SetGroupColumn(int aCol, bool aGroup)
void SetFilter(const wxString &aFilter)
wxString GetColLabelValue(int aCol) override
void ApplyBomPreset(const BOM_PRESET &aPreset)
virtual void RebuildRows()=0
DIALOG_FIELDS_TABLE * m_dialog
FIELDS_TABLE_GRID_TRICKS(DIALOG_FIELDS_TABLE *aDialog, WX_GRID *aGrid, FIELDS_TABLE_DATA_MODEL_BASE *aDataModel)
virtual void showFieldsTablePopupMenu(wxMenu &aMenu, wxGridEvent &aEvent)
void appendPopupMenuItems(wxMenu &aMenu) override
void copyFromVariant(const wxString &aVariant, const std::vector< wxGridCellCoords > &aCells)
FIELDS_TABLE_DATA_MODEL_BASE * m_dataModel
void doPopupSelection(wxCommandEvent &aEvent) override
void showPopupMenu(wxMenu &aMenu, wxGridEvent &aEvent) override
virtual void doFieldsTablePopupSelection(wxCommandEvent &aEvent)
A text control validator used for validating the text allowed in fields.
Definition validators.h:138
This class works around a bug in wxGrid where the first keystroke doesn't get sent through the valida...
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition grid_tricks.h:57
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.
wxString m_stringDelimiter
wxString m_fieldDelimiter
bool m_includeByteOrderMark
wxString m_filterString
std::vector< wxString > m_fieldsOrdered
wxString m_refRangeDelimiter
std::vector< wxString > m_fieldsLabels
wxString m_refDelimiter
void SetSelectedVariant(const wxString &aVariant)
std::vector< wxString > m_fieldsGroupBy
wxString m_bomFmtPresetName
wxString m_sortField
wxString m_bomPresetName
BOM_FILTER_SCOPE m_filterScope
void SetConfiguredOutputPath(const wxString &aPath)
Sets the configured output path for the job, this path is always saved to file.
Definition job.cpp:157
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
static REPORTER & GetInstance()
Definition reporter.cpp:207
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:407
virtual ROW_STATE GetRowState(int aRow) const
Definition wx_grid.h:109
void SetColAttr(wxGridCellAttr *aAttr, int aCol) override
Definition wx_grid.h:90
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject)
Replace any environment variable & text variable references with their values.
Definition common.cpp:776
wxString GetGeneratedFieldDisplayName(const wxString &aSource)
Returns any variables unexpanded, e.g.
Definition common.cpp:481
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
wxString NormalizeFilePathForTextVars(const wxString &aPath)
Normalize a file path so its text variables survive ExpandTextVars.
Definition common.cpp:72
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:797
bool IsGeneratedField(const wxString &aFieldName)
Returns true if the entire string is generated, e.g is a single text var reference.
Definition common.cpp:494
@ INTERNAL
Definition common.h:94
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:165
bool IsOK(wxWindow *aParent, const wxString &aMessage)
Display a yes/no dialog with aMessage and returns the user response.
Definition confirm.cpp:274
void DisplayInfoMessage(wxWindow *aParent, const wxString &aMessage, const wxString &aExtraInfo)
Display an informational message box with aMessage.
Definition confirm.cpp:245
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
void DisplayError(wxWindow *aParent, const wxString &aText)
Display an error or warning message box with aMessage.
Definition confirm.cpp:192
This file is part of the common library.
#define COLUMN_MARGIN
#define _(s)
Base window classes and related definitions.
@ GRIDTRICKS_FIRST_SHOWHIDE
Definition grid_tricks.h:47
@ GRIDTRICKS_FIRST_CLIENT_ID
Definition grid_tricks.h:44
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:533
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:96
std::vector< FAB_LAYER_COLOR > dummy
wxString GetDefaultVariantName()
wxString label
wxString name
wxString fieldDelimiter
bool includeByteOrderMark
static BOM_FMT_PRESET CSV()
static std::vector< BOM_FMT_PRESET > BuiltInPresets()
wxString stringDelimiter
wxString refRangeDelimiter
wxString refDelimiter
Dialog settings for the lib/symbol/footprint table editors across the various editors.
bool FieldNamesAreDuplicates(const wxString &aLhs, const wxString &aRhs, std::initializer_list< FIELD_T > aMandatoryFields)
Test whether two field names should be treated as duplicates for the purposes of field name uniquenes...
wxString GetDefaultFieldName(FIELD_T aFieldId, TRANSLATION aTranslation)
Return a default symbol field name for a mandatory field type.
#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...
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ DATASHEET
name of datasheet
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ UNTRANSLATED
std::string path
Functions to provide common constants and other functions to assist in making a consistent UI.
Custom text control validator definitions.
Definition of file extensions used in Kicad.
bool IsRowCollapsed(ROW_STATE aState)
Definition wx_grid.h:69
enum KICOMMON_API ROW_STATE
Used primarily by the fields tables to collapse multiple grouped symbols, etc.
Definition wx_grid.h:44
bool IsRowExpanded(ROW_STATE aState)
Definition wx_grid.h:75