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 = ResolveProjectPath( m_outputFileName->GetValue(), &Prj() );
1406
1407 // Calculate the export filename
1408 wxFileName fn( Prj().AbsolutePath( m_parentFrame->GetCurrentFileName() ) );
1409 fn.SetExt( FILEEXT::CsvFileExtension );
1410
1411 wxFileDialog saveDlg( this, _( "Bill of Materials Output File" ), path, fn.GetFullName(),
1412 FILEEXT::CsvFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
1413
1415
1416 if( saveDlg.ShowModal() == wxID_CANCEL )
1417 return;
1418
1419 wxFileName file = wxFileName( saveDlg.GetPath() );
1420 wxString defaultPath = fn.GetPathWithSep();
1421
1422 if( IsOK( this, wxString::Format( _( "Do you want to use a path relative to\n'%s'?" ), defaultPath ) ) )
1423 {
1424 if( !file.MakeRelativeTo( defaultPath ) )
1425 {
1426 DisplayErrorMessage( this, _( "Cannot make path relative (target volume different from file "
1427 "volume)!" ) );
1428 }
1429 }
1430
1431 m_outputFileName->SetValue( file.GetFullPath() );
1432}
1433
1434
1435void DIALOG_FIELDS_TABLE::OnExport( wxCommandEvent& aEvent )
1436{
1437 if( getDataModel()->IsEdited() )
1438 {
1439 if( OKOrCancelDialog( nullptr, _( "Unsaved data" ),
1440 _( "Changes have not yet been saved. Export unsaved data?" ), "", _( "OK" ),
1441 _( "Cancel" ) )
1442 == wxID_CANCEL )
1443 {
1444 return;
1445 }
1446 }
1447
1448 // Create output directory if it does not exist (also transform it in absolute form).
1449 // Bail if it fails.
1450
1451 std::function<bool( wxString* )> textResolver =
1452 [&]( wxString* token ) -> bool
1453 {
1454 return resolveTextVar( token );
1455 };
1456
1457 wxString sourceFileName = m_parentFrame->GetCurrentFileName();
1458 wxString path = m_outputFileName->GetValue();
1459
1460 if( path.IsEmpty() )
1461 {
1462 // Match the behaviour of other exporters and default to the source filename with a CSV
1463 // extension in the project directory when the user leaves the field blank.
1464 path = GetDefaultBomFileName( sourceFileName );
1465
1466 if( path.IsEmpty() )
1467 {
1468 DisplayError( this, _( "No output file specified in Export tab." ) );
1469 return;
1470 }
1471
1472 m_outputFileName->SetValue( path );
1473 }
1474
1477
1478 wxFileName outputFile = wxFileName::FileName( path );
1479 wxString msg;
1480
1481 if( !EnsureFileDirectoryExists( &outputFile, Prj().AbsolutePath( sourceFileName ), &NULL_REPORTER::GetInstance() ) )
1482 {
1483 msg.Printf( _( "Could not open/create path '%s'." ), outputFile.GetPath() );
1484 DisplayError( this, msg );
1485 return;
1486 }
1487
1488 wxFFile out( outputFile.GetFullPath(), "wb" );
1489
1490 if( !out.IsOpened() )
1491 {
1492 msg.Printf( _( "Could not create BOM output '%s'." ), outputFile.GetFullPath() );
1493 DisplayError( this, msg );
1494 return;
1495 }
1496
1498
1499 if( !out.Write( m_textOutput->GetValue() ) )
1500 {
1501 msg.Printf( _( "Could not write BOM output '%s'." ), outputFile.GetFullPath() );
1502 DisplayError( this, msg );
1503 return;
1504 }
1505
1506 // close the file before we tell the user it's done with the info modal :workflow meme:
1507 out.Close();
1508
1509 if( m_cfgBomSettings.m_BomExportFileName != m_outputFileName->GetValue() )
1510 {
1511 m_cfgBomSettings.m_BomExportFileName = m_outputFileName->GetValue();
1513 }
1514
1515 msg.Printf( _( "Wrote BOM output to '%s'" ), outputFile.GetFullPath() );
1516 DisplayInfoMessage( this, msg );
1517}
1518
1519
1521{
1522 bool saveIncludeExcudedFromBOM = getDataModel()->GetIncludeExcludedFromBOM();
1523
1526
1527 m_textOutput->SetValue( getDataModel()->Export( GetCurrentBomFmtSettings() ) );
1528
1529 if( saveIncludeExcudedFromBOM )
1530 {
1533 }
1534}
1535
1536
1538{
1539 wxString retv;
1540
1541 int selection = m_variantListBox->GetSelection();
1542
1543 if( ( selection == wxNOT_FOUND ) || ( m_variantListBox->GetString( selection ) == GetDefaultVariantName() ) )
1544 return retv;
1545
1546 return m_variantListBox->GetString( selection );
1547}
1548
1549
1550std::vector<BOM_PRESET> DIALOG_FIELDS_TABLE::GetUserBomPresets() const
1551{
1552 std::vector<BOM_PRESET> ret;
1553
1554 for( const std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
1555 {
1556 if( !pair.second.readOnly )
1557 ret.emplace_back( pair.second );
1558 }
1559
1560 return ret;
1561}
1562
1563
1564void DIALOG_FIELDS_TABLE::SetUserBomPresets( std::vector<BOM_PRESET>& aPresetList )
1565{
1566 // Reset to defaults
1568
1569 for( const BOM_PRESET& preset : aPresetList )
1570 {
1571 if( m_bomPresets.count( preset.name ) )
1572 continue;
1573
1574 m_bomPresets[preset.name] = preset;
1575
1576 m_bomPresetMRU.Add( preset.name );
1577 }
1578
1580}
1581
1582
1583void DIALOG_FIELDS_TABLE::ApplyBomPreset( const wxString& aPresetName )
1584{
1585 updateBomPresetSelection( aPresetName );
1586
1587 wxCommandEvent dummy;
1589}
1590
1591
1592void DIALOG_FIELDS_TABLE::ApplyBomPreset( const BOM_PRESET& aPreset )
1593{
1594 BOM_PRESET preset = aPreset;
1595
1596 if( preset.fieldsOrdered.empty() )
1597 {
1598 for( const BOM_PRESET& builtIn : getBuiltInBomPresets() )
1599 {
1600 if( builtIn.name == BOM_PRESET::DefaultEditing().name )
1601 {
1602 preset = builtIn;
1603 break;
1604 }
1605 }
1606 }
1607
1608 if( m_bomPresets.count( preset.name ) )
1609 m_currentBomPreset = &m_bomPresets[preset.name];
1610 else
1611 m_currentBomPreset = nullptr;
1612
1613 if( m_currentBomPreset && !m_currentBomPreset->readOnly )
1615 else
1616 m_lastSelectedBomPreset = nullptr;
1617
1618 updateBomPresetSelection( preset.name );
1619 doApplyBomPreset( preset );
1620}
1621
1622
1623void DIALOG_FIELDS_TABLE::doApplyBomPreset( const BOM_PRESET& aPreset )
1624{
1625 // Disable rebuilds while we're applying the preset otherwise we'll be
1626 // rebuilding the model constantly while firing off wx events
1628
1629 // Basically, we apply the BOM preset to the data model and then
1630 // update our UI to reflect resulting the data model state, not the preset.
1632 getDataModel()->ApplyBomPreset( aPreset );
1633
1634 // BOM Presets can add, but not remove, columns, so make sure the view controls
1635 // grid has all of them before starting
1636 for( int i = 0; i < getDataModel()->GetColsCount(); i++ )
1637 {
1638 const wxString& fieldName( getDataModel()->GetColFieldName( i ) );
1639 bool found = false;
1640
1641 for( int j = 0; j < m_viewControlsDataModel->GetNumberRows(); j++ )
1642 {
1643 if( m_viewControlsDataModel->GetUntranslatedFieldName( j ) == fieldName )
1644 {
1645 found = true;
1646 break;
1647 }
1648 }
1649
1650 // Properties like label, etc. will be added in the next loop
1651 if( !found )
1652 AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), false, false );
1653 }
1654
1655 // Sync all fields
1656 for( int i = 0; i < m_viewControlsDataModel->GetNumberRows(); i++ )
1657 {
1658 const wxString& fieldName( m_viewControlsDataModel->GetUntranslatedFieldName( i ) );
1659 int col = getDataModel()->GetFieldNameCol( fieldName );
1660
1661 if( col == -1 )
1662 {
1663 wxASSERT_MSG( true, "Fields control has a field not found in the data model." );
1664 continue;
1665 }
1666
1667 std::string fieldNameStr( fieldName.ToUTF8() );
1668
1669 // Set column labels
1670 const wxString& label = getDataModel()->GetColLabelValue( col );
1671 m_viewControlsDataModel->SetValue( i, LABEL_COLUMN, label );
1672 m_grid->SetColLabelValue( col, label );
1673
1674 if( m_cfgDialogSettings.field_widths.count( fieldNameStr ) )
1675 m_grid->SetColSize( col, m_cfgDialogSettings.field_widths.at( fieldNameStr ) );
1676
1677 // Set shown columns
1678 bool show = getDataModel()->GetShowColumn( col );
1679 m_viewControlsDataModel->SetValueAsBool( i, SHOW_FIELD_COLUMN, show );
1680
1681 if( show )
1682 m_grid->ShowCol( col );
1683 else
1684 m_grid->HideCol( col );
1685
1686 // Set grouped columns
1687 bool groupBy = getDataModel()->GetGroupColumn( col );
1688 m_viewControlsDataModel->SetValueAsBool( i, GROUP_BY_COLUMN, groupBy );
1689 }
1690
1691 m_grid->SetSortingColumn( getDataModel()->GetSortCol(), getDataModel()->GetSortAsc() );
1692 m_groupSymbolsBox->SetValue( getDataModel()->GetGroupingEnabled() );
1693 m_filter->ChangeValue( getDataModel()->GetFilter() );
1694 m_filterScope->SetSelection( static_cast<int>( getDataModel()->GetFilterScope() ) );
1695
1696 // Rebuild before sizing columns so their widths account for the current row data.
1699
1701
1702 if( m_nbPages->GetSelection() == 1 )
1704 else
1705 m_grid->ForceRefresh();
1706}
1707
1708
1710{
1711 m_bomPresets.clear();
1712 m_bomPresetMRU.clear();
1713
1714 // Load the read-only defaults
1715 for( const BOM_PRESET& preset : getBuiltInBomPresets() )
1716 {
1717 m_bomPresets[preset.name] = preset;
1718 m_bomPresets[preset.name].readOnly = true;
1719
1720 m_bomPresetMRU.Add( preset.name );
1721 }
1722}
1723
1724
1725std::vector<BOM_PRESET> DIALOG_FIELDS_TABLE::getBuiltInBomPresets() const
1726{
1727 return BOM_PRESET::BuiltInPresets();
1728}
1729
1730
1732{
1733 m_cbBomPresets->Clear();
1734
1735 int idx = 0;
1736 int default_idx = 0;
1737
1738 for( const auto& [presetName, preset] : m_bomPresets )
1739 {
1740 wxString label = preset.readOnly ? wxGetTranslation( presetName ) : presetName;
1741 m_cbBomPresets->Append( label, (void*) &preset );
1742
1743 if( presetName == BOM_PRESET::DefaultEditing().name )
1744 default_idx = idx;
1745
1746 idx++;
1747 }
1748
1749 m_cbBomPresets->Append( wxT( "---" ) );
1750
1751 if( !m_job )
1752 {
1753 m_cbBomPresets->Append( _( "Save preset..." ) );
1754 m_cbBomPresets->Append( _( "Delete preset..." ) );
1755 }
1756
1757 // At least the built-in presets should always be present
1758 wxASSERT( !m_bomPresets.empty() );
1759
1760 m_cbBomPresets->SetSelection( default_idx );
1761 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( default_idx ) );
1762}
1763
1764
1766{
1767 return aPresetCount - ( m_job ? 1 : 3 );
1768}
1769
1770
1772{
1773 return m_job ? wxNOT_FOUND : aPresetCount - 2;
1774}
1775
1776
1778{
1779 return m_job ? wxNOT_FOUND : aPresetCount - 1;
1780}
1781
1782
1787
1788
1790{
1791 BOM_PRESET current = getDataModelBomPreset();
1792
1793 auto it = std::find_if( m_bomPresets.begin(), m_bomPresets.end(),
1794 [&]( const std::pair<const wxString, BOM_PRESET>& aPair )
1795 {
1796 const BOM_PRESET& preset = aPair.second;
1797
1798 // Check the simple settings first
1799 if( !( preset.sortAsc == current.sortAsc
1800 && preset.filterString == current.filterString
1801 && preset.filterScope == current.filterScope
1802 && preset.groupSymbols == current.groupSymbols
1803 && preset.excludeDNP == current.excludeDNP
1804 && preset.includeExcludedFromBOM == current.includeExcludedFromBOM ) )
1805 {
1806 return false;
1807 }
1808
1809 if( preset.sortField != current.sortField )
1810 return false;
1811
1812 // Only compare shown or grouped fields
1813 std::vector<BOM_FIELD> A, B;
1814
1815 for( const BOM_FIELD& field : preset.fieldsOrdered )
1816 {
1817 if( field.show || field.groupBy )
1818 A.emplace_back( field );
1819 }
1820
1821 for( const BOM_FIELD& field : current.fieldsOrdered )
1822 {
1823 if( field.show || field.groupBy )
1824 B.emplace_back( field );
1825 }
1826
1827 return A == B;
1828 } );
1829
1830 if( it != m_bomPresets.end() )
1831 {
1832 m_cbBomPresets->SetSelection( findPresetIndexByPointer( m_cbBomPresets, &it->second ) );
1833 }
1834 else
1835 {
1836 m_cbBomPresets->SetSelection( presetDashDashDashIndex( m_cbBomPresets->GetCount() ) );
1837 }
1838
1839 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( m_cbBomPresets->GetSelection() ) );
1840}
1841
1842
1844{
1845 int idx = findPresetIndexByName( m_cbBomPresets, m_bomPresets, aName );
1846
1847 if( idx >= 0 && m_cbBomPresets->GetSelection() != idx )
1848 {
1849 m_cbBomPresets->SetSelection( idx );
1850 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( idx ) );
1851 }
1852 else if( idx < 0 )
1853 {
1854 m_cbBomPresets->SetSelection( presetDashDashDashIndex( m_cbBomPresets->GetCount() ) );
1855 }
1856}
1857
1858
1859void DIALOG_FIELDS_TABLE::onBomPresetChanged( wxCommandEvent& aEvent )
1860{
1861 int count = m_cbBomPresets->GetCount();
1862 int index = m_cbBomPresets->GetSelection();
1863
1864 auto resetSelection =
1865 [&]()
1866 {
1867 int presetIndex = findPresetIndexByPointer( m_cbBomPresets, m_currentBomPreset );
1868
1869 if( presetIndex == wxNOT_FOUND )
1870 m_cbBomPresets->SetSelection( presetDashDashDashIndex( m_cbBomPresets->GetCount() ) );
1871 else
1872 m_cbBomPresets->SetSelection( presetIndex );
1873 };
1874
1875 if( index == presetDashDashDashIndex( count ) )
1876 {
1877 // Separator: reject the selection
1878 resetSelection();
1879 return;
1880 }
1881 else if( index == presetSavePresetIndex( count ) )
1882 {
1883 // Save current state to new preset
1884 wxString name;
1885
1888
1889 wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
1890
1891 if( dlg.ShowModal() != wxID_OK )
1892 {
1893 resetSelection();
1894 return;
1895 }
1896
1897 name = dlg.GetValue();
1898 bool exists = m_bomPresets.count( name );
1899
1900 if( !exists )
1901 {
1903 m_bomPresets[name].readOnly = false;
1904 m_bomPresets[name].name = name;
1905 }
1906
1907 BOM_PRESET* preset = &m_bomPresets[name];
1908
1909 if( !exists )
1910 {
1911 index = m_cbBomPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
1912 }
1913 else if( preset->readOnly )
1914 {
1915 wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
1916 _( "Error" ), wxOK | wxICON_ERROR, this );
1917 resetSelection();
1918 return;
1919 }
1920 else
1921 {
1922 // Ask the user if they want to overwrite the existing preset
1923 if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
1924 {
1925 resetSelection();
1926 return;
1927 }
1928
1929 *preset = getDataModelBomPreset();
1930 preset->name = name;
1931
1932 index = findPresetIndexByPointer( m_cbBomPresets, preset );
1933
1934 if( m_bomPresetMRU.Index( name ) != wxNOT_FOUND )
1935 m_bomPresetMRU.Remove( name );
1936 }
1937
1938 m_currentBomPreset = preset;
1939 m_cbBomPresets->SetSelection( index );
1940 m_bomPresetMRU.Insert( name, 0 );
1941
1942 return;
1943 }
1944 else if( index == presetDeletePresetIndex( count ) )
1945 {
1946 // Delete a preset
1947 wxArrayString headers;
1948 std::vector<wxArrayString> items;
1949
1950 headers.Add( _( "Presets" ) );
1951
1952 for( const auto& [name, preset] : m_bomPresets )
1953 {
1954 if( !preset.readOnly )
1955 {
1956 wxArrayString item;
1957 item.Add( name );
1958 items.emplace_back( item );
1959 }
1960 }
1961
1962 EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
1963 dlg.SetListLabel( _( "Select preset:" ) );
1964
1965 if( dlg.ShowModal() == wxID_OK )
1966 {
1967 wxString presetName = dlg.GetTextSelection();
1968 auto presetIt = m_bomPresets.find( presetName );
1969 int idx = findPresetIndexByName( m_cbBomPresets, m_bomPresets, presetName );
1970
1971 if( idx != wxNOT_FOUND && presetIt != m_bomPresets.end() )
1972 {
1973 if( m_lastSelectedBomPreset == &presetIt->second )
1974 m_lastSelectedBomPreset = nullptr;
1975
1976 m_currentBomPreset = nullptr;
1977 m_cbBomPresets->Delete( idx );
1978 m_bomPresets.erase( presetIt );
1979 }
1980
1981 if( m_bomPresetMRU.Index( presetName ) != wxNOT_FOUND )
1982 m_bomPresetMRU.Remove( presetName );
1983 }
1984
1985 resetSelection();
1986 return;
1987 }
1988
1989 BOM_PRESET* preset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( index ) );
1990 m_currentBomPreset = preset;
1991
1992 m_lastSelectedBomPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
1993
1994 if( preset )
1995 {
1996 doApplyBomPreset( *preset );
1998 m_currentBomPreset = preset;
1999
2000 if( !m_currentBomPreset->name.IsEmpty() )
2001 {
2002 if( m_bomPresetMRU.Index( preset->name ) != wxNOT_FOUND )
2003 m_bomPresetMRU.Remove( preset->name );
2004
2005 m_bomPresetMRU.Insert( preset->name, 0 );
2006 }
2007 }
2008}
2009
2010
2012{
2013 BOM_FMT_PRESET current;
2014
2015 int selection = m_cbBomFmtPresets->GetSelection();
2016
2017 if( selection != wxNOT_FOUND )
2018 {
2019 if( auto* preset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( selection ) ) )
2020 current.name = preset->name;
2021 else
2022 current.name = m_cbBomFmtPresets->GetString( selection );
2023 }
2024
2025 current.fieldDelimiter = m_textFieldDelimiter->GetValue();
2026 current.stringDelimiter = m_textStringDelimiter->GetValue();
2027 current.refDelimiter = m_textRefDelimiter->GetValue();
2028 current.refRangeDelimiter = m_textRefRangeDelimiter->GetValue();
2029 current.keepTabs = m_checkKeepTabs->GetValue();
2030 current.keepLineBreaks = m_checkKeepLineBreaks->GetValue();
2032
2033 return current;
2034}
2035
2036
2037std::vector<BOM_FMT_PRESET> DIALOG_FIELDS_TABLE::GetUserBomFmtPresets() const
2038{
2039 std::vector<BOM_FMT_PRESET> ret;
2040
2041 for( const auto& [name, preset] : m_bomFmtPresets )
2042 {
2043 if( !preset.readOnly )
2044 ret.emplace_back( preset );
2045 }
2046
2047 return ret;
2048}
2049
2050
2051void DIALOG_FIELDS_TABLE::SetUserBomFmtPresets( std::vector<BOM_FMT_PRESET>& aPresetList )
2052{
2053 // Reset to defaults
2055
2056 for( const BOM_FMT_PRESET& preset : aPresetList )
2057 {
2058 if( m_bomFmtPresets.count( preset.name ) )
2059 continue;
2060
2061 m_bomFmtPresets[preset.name] = preset;
2062
2063 m_bomFmtPresetMRU.Add( preset.name );
2064 }
2065
2067}
2068
2069
2070void DIALOG_FIELDS_TABLE::ApplyBomFmtPreset( const wxString& aPresetName )
2071{
2072 updateBomFmtPresetSelection( aPresetName );
2073
2074 wxCommandEvent dummy;
2076}
2077
2078
2080{
2081 m_currentBomFmtPreset = nullptr;
2083
2084 if( m_bomFmtPresets.count( aPreset.name ) )
2086
2089
2091 doApplyBomFmtPreset( aPreset );
2092}
2093
2094
2096{
2097 m_textFieldDelimiter->ChangeValue( aPreset.fieldDelimiter );
2098 m_textStringDelimiter->ChangeValue( aPreset.stringDelimiter );
2099 m_textRefDelimiter->ChangeValue( aPreset.refDelimiter );
2100 m_textRefRangeDelimiter->ChangeValue( aPreset.refRangeDelimiter );
2101 m_checkKeepTabs->SetValue( aPreset.keepTabs );
2102 m_checkKeepLineBreaks->SetValue( aPreset.keepLineBreaks );
2104
2105 // Refresh the preview if that's the current page
2106 if( m_nbPages->GetSelection() == 1 )
2108}
2109
2110
2112{
2113 m_bomFmtPresets.clear();
2114 m_bomFmtPresetMRU.clear();
2115
2116 // Load the read-only defaults
2117 for( const BOM_FMT_PRESET& preset : BOM_FMT_PRESET::BuiltInPresets() )
2118 {
2119 m_bomFmtPresets[preset.name] = preset;
2120 m_bomFmtPresets[preset.name].readOnly = true;
2121
2122 m_bomFmtPresetMRU.Add( preset.name );
2123 }
2124}
2125
2126
2128{
2129 m_cbBomFmtPresets->Clear();
2130
2131 int idx = 0;
2132 int default_idx = 0;
2133
2134 for( const auto& [presetName, preset] : m_bomFmtPresets )
2135 {
2136 wxString label = preset.readOnly ? wxGetTranslation( presetName ) : presetName;
2137 m_cbBomFmtPresets->Append( label, (void*) &preset );
2138
2139 if( presetName == BOM_FMT_PRESET::CSV().name )
2140 default_idx = idx;
2141
2142 idx++;
2143 }
2144
2145 m_cbBomFmtPresets->Append( wxT( "---" ) );
2146
2147 if( !m_job )
2148 {
2149 m_cbBomFmtPresets->Append( _( "Save preset..." ) );
2150 m_cbBomFmtPresets->Append( _( "Delete preset..." ) );
2151 }
2152
2153 // At least the built-in presets should always be present
2154 wxASSERT( !m_bomFmtPresets.empty() );
2155
2156 m_cbBomFmtPresets->SetSelection( default_idx );
2157 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( default_idx ) );
2158}
2159
2160
2162{
2164
2165 auto it = std::find_if( m_bomFmtPresets.begin(), m_bomFmtPresets.end(),
2166 [&]( const std::pair<const wxString, BOM_FMT_PRESET>& aPair )
2167 {
2168 return ( aPair.second.fieldDelimiter == current.fieldDelimiter
2169 && aPair.second.stringDelimiter == current.stringDelimiter
2170 && aPair.second.refDelimiter == current.refDelimiter
2171 && aPair.second.refRangeDelimiter == current.refRangeDelimiter
2172 && aPair.second.keepTabs == current.keepTabs
2173 && aPair.second.keepLineBreaks == current.keepLineBreaks
2174 && aPair.second.includeByteOrderMark == current.includeByteOrderMark );
2175 } );
2176
2177 if( it != m_bomFmtPresets.end() )
2178 {
2179 m_cbBomFmtPresets->SetSelection( findPresetIndexByPointer( m_cbBomFmtPresets, &it->second ) );
2180 }
2181 else
2182 {
2183 m_cbBomFmtPresets->SetSelection( presetDashDashDashIndex( m_cbBomFmtPresets->GetCount() ) );
2184 }
2185
2186 int idx = m_cbBomFmtPresets->GetSelection();
2187 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
2188}
2189
2190
2192{
2193 int idx = findPresetIndexByName( m_cbBomFmtPresets, m_bomFmtPresets, aName );
2194
2195 if( idx >= 0 && m_cbBomFmtPresets->GetSelection() != idx )
2196 {
2197 m_cbBomFmtPresets->SetSelection( idx );
2198 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
2199 }
2200 else if( idx < 0 )
2201 {
2202 m_cbBomFmtPresets->SetSelection( presetDashDashDashIndex( m_cbBomFmtPresets->GetCount() ) );
2203 }
2204}
2205
2206
2208{
2209 int count = m_cbBomFmtPresets->GetCount();
2210 int index = m_cbBomFmtPresets->GetSelection();
2211
2212 auto resetSelection =
2213 [&]()
2214 {
2215 int presetIndex = findPresetIndexByPointer( m_cbBomFmtPresets, m_currentBomFmtPreset );
2216
2217 if( presetIndex == wxNOT_FOUND )
2218 m_cbBomFmtPresets->SetSelection( presetDashDashDashIndex( m_cbBomFmtPresets->GetCount() ) );
2219 else
2220 m_cbBomFmtPresets->SetSelection( presetIndex );
2221 };
2222
2223 if( index == presetDashDashDashIndex( count ) )
2224 {
2225 // Separator: reject the selection
2226 resetSelection();
2227 return;
2228 }
2229 else if( index == presetSavePresetIndex( count ) )
2230 {
2231 // Save current state to new preset
2232 wxString name;
2233
2236
2237 wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
2238
2239 if( dlg.ShowModal() != wxID_OK )
2240 {
2241 resetSelection();
2242 return;
2243 }
2244
2245 name = dlg.GetValue();
2246 bool exists = m_bomFmtPresets.count( name );
2247
2248 if( !exists )
2249 {
2251 m_bomFmtPresets[name].readOnly = false;
2252 m_bomFmtPresets[name].name = name;
2253 }
2254
2256
2257 if( !exists )
2258 {
2259 index = m_cbBomFmtPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
2260 }
2261 else if( preset->readOnly )
2262 {
2263 wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
2264 _( "Error" ), wxOK | wxICON_ERROR, this );
2265 resetSelection();
2266 return;
2267 }
2268 else
2269 {
2270 // Ask the user if they want to overwrite the existing preset
2271 if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
2272 {
2273 resetSelection();
2274 return;
2275 }
2276
2277 *preset = GetCurrentBomFmtSettings();
2278 preset->name = name;
2279
2280 index = findPresetIndexByPointer( m_cbBomFmtPresets, preset );
2281
2282 if( m_bomFmtPresetMRU.Index( name ) != wxNOT_FOUND )
2283 m_bomFmtPresetMRU.Remove( name );
2284 }
2285
2286 m_currentBomFmtPreset = preset;
2287 m_cbBomFmtPresets->SetSelection( index );
2288 m_bomFmtPresetMRU.Insert( name, 0 );
2289
2290 return;
2291 }
2292 else if( index == presetDeletePresetIndex( count ) )
2293 {
2294 // Delete a preset
2295 wxArrayString headers;
2296 std::vector<wxArrayString> items;
2297
2298 headers.Add( _( "Presets" ) );
2299
2300 for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
2301 {
2302 if( !pair.second.readOnly )
2303 {
2304 wxArrayString item;
2305 item.Add( pair.first );
2306 items.emplace_back( item );
2307 }
2308 }
2309
2310 EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
2311 dlg.SetListLabel( _( "Select preset:" ) );
2312
2313 if( dlg.ShowModal() == wxID_OK )
2314 {
2315 wxString presetName = dlg.GetTextSelection();
2316 auto presetIt = m_bomFmtPresets.find( presetName );
2317 int idx = findPresetIndexByName( m_cbBomFmtPresets, m_bomFmtPresets, presetName );
2318
2319 if( idx != wxNOT_FOUND && presetIt != m_bomFmtPresets.end() )
2320 {
2321 if( m_lastSelectedBomFmtPreset == &presetIt->second )
2323
2324 m_currentBomFmtPreset = nullptr;
2325 m_cbBomFmtPresets->Delete( idx );
2326 m_bomFmtPresets.erase( presetIt );
2327 }
2328
2329 if( m_bomFmtPresetMRU.Index( presetName ) != wxNOT_FOUND )
2330 m_bomFmtPresetMRU.Remove( presetName );
2331 }
2332
2333 resetSelection();
2334 return;
2335 }
2336
2337 auto* preset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( index ) );
2338 m_currentBomFmtPreset = preset;
2339
2340 m_lastSelectedBomFmtPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
2341
2342 if( preset )
2343 {
2344 doApplyBomFmtPreset( *preset );
2346 m_currentBomFmtPreset = preset;
2347
2348 if( !m_currentBomFmtPreset->name.IsEmpty() )
2349 {
2350 if( m_bomFmtPresetMRU.Index( preset->name ) != wxNOT_FOUND )
2351 m_bomFmtPresetMRU.Remove( preset->name );
2352
2353 m_bomFmtPresetMRU.Insert( preset->name, 0 );
2354 }
2355 }
2356}
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:156
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
static REPORTER & GetInstance()
Definition reporter.cpp:207
virtual ROW_STATE GetRowState(int aRow) const
Definition wx_grid.h:109
void SetColAttr(wxGridCellAttr *aAttr, int aCol) override
Definition wx_grid.h:90
wxString GetGeneratedFieldDisplayName(const wxString &aSource)
Returns any variables unexpanded, e.g.
Definition common.cpp:481
const wxString ExpandEnvVarSubstitutions(const wxString &aString, const PROJECT *aProject, bool aAllowEscapes)
Replace any environment variable & text variable references with their values.
Definition common.cpp:777
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, RESOLUTION_CONTEXT aContext)
Definition common.cpp:60
wxString ResolveProjectPath(const wxString &aPath, const PROJECT *aProject, const std::function< bool(wxString *)> *aTextResolver)
Expand a user-entered file path as ExpandPathVariables() does, then anchor a relative result at the p...
Definition common.cpp:817
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:829
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:96
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:640
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:79
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