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{
63enum
64{
65 MYID_REVERT_ROW = GRIDTRICKS_FIRST_CLIENT_ID,
66 MYID_CLEAR_CELL
67};
68
69
70class VIEW_CONTROLS_GRID_TRICKS : public GRID_TRICKS
71{
72public:
73 VIEW_CONTROLS_GRID_TRICKS( WX_GRID* aGrid ) :
74 GRID_TRICKS( aGrid )
75 {
76 }
77
78protected:
79 void doPopupSelection( wxCommandEvent& aEvent ) override
80 {
81 if( aEvent.GetId() >= GRIDTRICKS_FIRST_SHOWHIDE )
82 m_grid->PostSizeEvent();
83
85 }
86};
87} // namespace
88
89
91 FIELDS_TABLE_DATA_MODEL_BASE* aDataModel ) :
92 GRID_TRICKS( aGrid ),
93 m_dialog( aDialog ),
94 m_dataModel( aDataModel )
95{
96}
97
98
99void FIELDS_TABLE_GRID_TRICKS::showPopupMenu( wxMenu& aMenu, wxGridEvent& aEvent )
100{
101 int row = m_grid->GetGridCursorRow();
102 int col = m_grid->GetGridCursorCol();
103
104 wxMenuItem* revertMenu = aMenu.Append( MYID_REVERT_ROW, _( "Revert Row" ),
105 _( "Revert the fields in this row" ) );
106 wxMenuItem* clearMenu = aMenu.Append( MYID_CLEAR_CELL, _( "Clear Cell" ),
107 _( "Clear the cell value" ) );
108 aMenu.AppendSeparator();
109
110 if( row >= 0 && col >= 0 )
111 {
112 revertMenu->Enable( m_grid->IsEditable() && m_dataModel->IsRowEdited( row ) );
113 clearMenu->Enable( m_grid->IsEditable() && m_dataModel->CanClearCell( row, col ) );
114 }
115 else
116 {
117 revertMenu->Enable( false );
118 clearMenu->Enable( false );
119 }
120
121 showFieldsTablePopupMenu( aMenu, aEvent );
122}
123
124
125void FIELDS_TABLE_GRID_TRICKS::doPopupSelection( wxCommandEvent& aEvent )
126{
127 int row = m_grid->GetGridCursorRow();
128 int col = m_grid->GetGridCursorCol();
129
130 if( aEvent.GetId() == MYID_REVERT_ROW )
131 {
132 if( m_grid->CommitPendingChanges( false ) )
133 m_dataModel->RevertRow( row );
134
135 if( m_dataModel->IsEdited() )
136 m_dialog->OnModify();
137 else
138 m_dialog->ClearModify();
139
140 m_grid->ForceRefresh();
141 }
142 else if( aEvent.GetId() == MYID_CLEAR_CELL )
143 {
144 if( m_grid->CommitPendingChanges( false ) )
145 m_dataModel->ClearCell( row, col );
146
147 if( m_dataModel->IsEdited() )
148 m_dialog->OnModify();
149 else
150 m_dialog->ClearModify();
151
152 m_grid->ForceRefresh();
153 }
154 else
155 {
157 }
158}
159
160
161void FIELDS_TABLE_GRID_TRICKS::showFieldsTablePopupMenu( wxMenu& aMenu, wxGridEvent& aEvent )
162{
163 GRID_TRICKS::showPopupMenu( aMenu, aEvent );
164}
165
166
168{
170}
171
172
174 FIELDS_TABLE_BOM_SETTINGS& aBomSettings,
175 JOB_EXPORT_BOM* aJob ) :
176 DIALOG_FIELDS_TABLE_BASE( aParent ),
177 m_cfgDialogSettings( aPanelSettings ),
178 m_cfgBomSettings( aBomSettings ),
179 m_job( aJob )
180{
182 m_bMenu->SetBitmap( KiBitmapBundle( BITMAPS::config ) );
185
189
195
197
198 // Don't save scope, we don't want users opening up the table for the first
199 // on program launch stuck with Selection scope and seeing a blank table
200 OptOut( m_scope );
201
203
204 m_viewControlsGrid->UseNativeColHeader( true );
206
207 // must be done after SetTable(), which appears to re-set it
208 m_viewControlsGrid->SetSelectionMode( wxGrid::wxGridSelectCells );
209
210 // add Cut, Copy, and Paste to wxGrid
211 m_viewControlsGrid->PushEventHandler( new VIEW_CONTROLS_GRID_TRICKS( m_viewControlsGrid ) );
212
213 wxGridCellAttr* attr = new wxGridCellAttr;
214 attr->SetReadOnly( true );
215 m_viewControlsDataModel->SetColAttr( attr, DISPLAY_NAME_COLUMN );
216
217 attr = new wxGridCellAttr;
218 attr->SetRenderer( new wxGridCellBoolRenderer() );
219 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
220 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
221 m_viewControlsDataModel->SetColAttr( attr, SHOW_FIELD_COLUMN );
222
223 attr = new wxGridCellAttr;
224 attr->SetRenderer( new wxGridCellBoolRenderer() );
225 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
226 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
227 m_viewControlsDataModel->SetColAttr( attr, GROUP_BY_COLUMN );
228
229 // Compress the view controls grid. (We want it to look different from the fields grid.)
230 m_viewControlsGrid->SetDefaultRowSize( m_viewControlsGrid->GetDefaultRowSize() - FromDIP( 4 ) );
231
232 m_filter->SetDescriptiveText( _( "Filter" ) );
233
234 m_grid->EnableCursorRowColumnHighlight();
235 m_grid->GetGridWindow()->Bind( wxEVT_MOUSEWHEEL, &DIALOG_FIELDS_TABLE::OnGridMouseWheel, this );
236}
237
238
240{
241 m_grid->GetGridWindow()->Unbind( wxEVT_MOUSEWHEEL, &DIALOG_FIELDS_TABLE::OnGridMouseWheel, this );
242 m_viewControlsGrid->PopEventHandler( true );
243}
244
245
247{
248 m_nbPages->SetSelection( 0 );
249}
250
251
253{
254 m_nbPages->SetSelection( 1 );
255}
256
257
258void DIALOG_FIELDS_TABLE::ShowHideColumn( int aCol, bool aShow )
259{
260 if( aShow )
261 m_grid->ShowCol( aCol );
262 else
263 m_grid->HideCol( aCol );
264
265 getDataModel()->SetShowColumn( aCol, aShow );
266
267 if( getDataModel()->GetFilterScope() == BOM_FILTER_SCOPE::VISIBLE
268 && !getDataModel()->GetFilter().IsEmpty() )
269 {
271 }
272
274
275 if( m_nbPages->GetSelection() == 1 )
277 else
278 m_grid->ForceRefresh();
279
280 OnModify();
281}
282
283
284void DIALOG_FIELDS_TABLE::AddField( const wxString& aFieldName, const wxString& aLabelValue, bool aShow, bool aGroupBy,
285 bool aAddedByUser )
286{
287 // Users can add fields with variable names that match the special names in the grid,
288 // e.g. ${QUANTITY} so make sure we don't add them twice
289 for( int row = 0; row < m_viewControlsDataModel->GetNumberRows(); row++ )
290 {
291 if( FieldNamesAreDuplicates( m_viewControlsDataModel->GetUntranslatedFieldName( row ), aFieldName ) )
292 {
293 return;
294 }
295 }
296
297 getDataModel()->AddColumn( aFieldName, aLabelValue, aAddedByUser );
298
299 m_viewControlsGrid->OnAddRow(
300 [&]() -> std::pair<int, int>
301 {
302 m_viewControlsDataModel->AppendRow( aFieldName, aLabelValue, aShow, aGroupBy );
303
304 return { m_viewControlsDataModel->GetNumberRows() - 1, -1 };
305 } );
306}
307
308
309wxString DIALOG_FIELDS_TABLE::GetDefaultBomFileName( const wxString& aInputFileName )
310{
311 if( aInputFileName.IsEmpty() )
312 return wxEmptyString;
313
314 wxFileName fn( aInputFileName );
315 fn.SetExt( FILEEXT::CsvFileExtension );
316
317 return fn.GetFullPath();
318}
319
320
321void DIALOG_FIELDS_TABLE::loadJobBomPreset( const JOB_EXPORT_BOM& aJob, BOM_PRESET& aPreset )
322{
323 aPreset.name = aJob.m_bomPresetName;
324 aPreset.excludeDNP = aJob.m_excludeDNP;
325 aPreset.filterString = aJob.m_filterString;
326 aPreset.filterScope = aJob.m_filterScope;
327 aPreset.sortAsc = aJob.m_sortAsc;
328 aPreset.sortField = aJob.m_sortField;
329 aPreset.groupSymbols = aJob.m_groupSymbols;
330
331 aPreset.fieldsOrdered.clear();
332
333 size_t i = 0;
334
335 for( const wxString& fieldName : aJob.m_fieldsOrdered )
336 {
337 BOM_FIELD field;
338 field.name = fieldName;
339 field.show = !fieldName.StartsWith( wxT( "__" ), &field.name );
340 field.groupBy = alg::contains( aJob.m_fieldsGroupBy, field.name );
341
342 if( ( aJob.m_fieldsLabels.size() > i ) && !aJob.m_fieldsLabels[i].IsEmpty() )
343 field.label = aJob.m_fieldsLabels[i];
344 else if( IsGeneratedField( field.name ) )
345 field.label = GetGeneratedFieldDisplayName( field.name );
346 else
347 field.label = field.name;
348
349 aPreset.fieldsOrdered.emplace_back( field );
350 i++;
351 }
352}
353
354
356{
357 aPreset.name = aJob.m_bomFmtPresetName;
358 aPreset.fieldDelimiter = aJob.m_fieldDelimiter;
359 aPreset.keepLineBreaks = aJob.m_keepLineBreaks;
360 aPreset.keepTabs = aJob.m_keepTabs;
362 aPreset.refDelimiter = aJob.m_refDelimiter;
364 aPreset.stringDelimiter = aJob.m_stringDelimiter;
365}
366
367
369{
370 aJob.SetConfiguredOutputPath( m_outputFileName->GetValue() );
371 aJob.m_bomFmtPresetName = wxEmptyString;
372 aJob.m_bomPresetName = wxEmptyString;
373
375 aJob.m_fieldDelimiter = fmtSettings.fieldDelimiter;
376 aJob.m_stringDelimiter = fmtSettings.stringDelimiter;
377 aJob.m_refDelimiter = fmtSettings.refDelimiter;
378 aJob.m_refRangeDelimiter = fmtSettings.refRangeDelimiter;
379 aJob.m_keepTabs = fmtSettings.keepTabs;
380 aJob.m_keepLineBreaks = fmtSettings.keepLineBreaks;
382
383 BOM_PRESET presetFields = getDataModel()->GetBomSettings();
384 aJob.m_sortAsc = presetFields.sortAsc;
385 aJob.m_excludeDNP = presetFields.excludeDNP;
386 aJob.m_filterString = presetFields.filterString;
387 aJob.m_filterScope = presetFields.filterScope;
388 aJob.m_sortField = presetFields.sortField;
389 aJob.m_groupSymbols = presetFields.groupSymbols;
390
391 aJob.m_fieldsOrdered.clear();
392 aJob.m_fieldsLabels.clear();
393 aJob.m_fieldsGroupBy.clear();
394
395 for( const BOM_FIELD& modelField : getDataModel()->GetFieldsOrdered() )
396 {
397 if( modelField.show )
398 aJob.m_fieldsOrdered.emplace_back( modelField.name );
399 else
400 aJob.m_fieldsOrdered.emplace_back( wxT( "__" ) + modelField.name );
401
402 aJob.m_fieldsLabels.emplace_back( modelField.label );
403
404 if( modelField.groupBy )
405 aJob.m_fieldsGroupBy.emplace_back( modelField.name );
406 }
407
409}
410
411
412bool DIALOG_FIELDS_TABLE::savePresets( bool aSaveCurrentSettings )
413{
414 bool modified = false;
415
416 std::vector<BOM_PRESET> presets = GetUserBomPresets();
417
418 if( m_cfgBomSettings.m_BomPresets != presets )
419 {
420 modified = true;
421 m_cfgBomSettings.m_BomPresets = presets;
422 }
423
424 BOM_PRESET bomSettings = getDataModel()->GetBomSettings();
425
426 if( aSaveCurrentSettings && m_cfgBomSettings.m_BomSettings != bomSettings )
427 {
428 modified = true;
429 m_cfgBomSettings.m_BomSettings = bomSettings;
430 }
431
432 std::vector<BOM_FMT_PRESET> fmts = GetUserBomFmtPresets();
433
434 if( m_cfgBomSettings.m_BomFmtPresets != fmts )
435 {
436 modified = true;
437 m_cfgBomSettings.m_BomFmtPresets = fmts;
438 }
439
441
442 if( aSaveCurrentSettings && m_cfgBomSettings.m_BomFmtSettings != fmtSettings )
443 {
444 modified = true;
445 m_cfgBomSettings.m_BomFmtSettings = fmtSettings;
446 }
447
448 return modified;
449}
450
451
453{
454 return ConvertDialogToPixels( wxSize( 600, 300 ) );
455}
456
457
459{
460 m_grid->Connect( wxEVT_GRID_RANGE_SELECTED,
461 wxGridRangeSelectEventHandler( DIALOG_FIELDS_TABLE::OnTableRangeSelectionChanged ), nullptr,
462 this );
463}
464
465
467{
468 m_grid->Disconnect( wxEVT_GRID_RANGE_SELECTED,
469 wxGridRangeSelectEventHandler( DIALOG_FIELDS_TABLE::OnTableRangeSelectionChanged ), nullptr,
470 this );
471}
472
473
478
479
481{
482 std::set<int> selectedRows;
483
484 wxGridCellCoordsArray topLeft = m_grid->GetSelectionBlockTopLeft();
485 wxGridCellCoordsArray bottomRight = m_grid->GetSelectionBlockBottomRight();
486
487 for( size_t i = 0; i < topLeft.size(); ++i )
488 {
489 for( int row = topLeft[i].GetRow(); row <= bottomRight[i].GetRow(); ++row )
490 selectedRows.insert( row );
491 }
492
493 wxGridCellCoordsArray selectedCells = m_grid->GetSelectedCells();
494
495 for( size_t i = 0; i < selectedCells.size(); ++i )
496 selectedRows.insert( selectedCells[i].GetRow() );
497
498 for( int row : m_grid->GetSelectedRows() )
499 selectedRows.insert( row );
500
501 int cursorRow = m_grid->GetGridCursorRow();
502
503 if( selectedRows.empty() && cursorRow >= 0 && cursorRow < getDataModel()->GetNumberRows() )
504 selectedRows.insert( cursorRow );
505
506 return selectedRows;
507}
508
509
511{
512 std::set<KIID_PATH> selectedItemKeys;
513
514 for( int row : GetSelectedGridRows() )
515 {
516 for( const KIID_PATH& key : getDataModel()->GetRowItemKeys( row ) )
517 selectedItemKeys.insert( key );
518 }
519
520 return selectedItemKeys;
521}
522
523
524void DIALOG_FIELDS_TABLE::RestoreGridSelection( const std::set<KIID_PATH>& aItemKeys )
525{
526 if( aItemKeys.empty() )
527 return;
528
529 m_grid->ClearSelection();
530
531 bool firstSelection = true;
532
533 for( int row = 0; row < getDataModel()->GetNumberRows(); ++row )
534 {
535 for( const KIID_PATH& key : getDataModel()->GetRowItemKeys( row ) )
536 {
537 if( aItemKeys.count( key ) )
538 {
539 m_grid->SelectRow( row, true );
540
541 if( firstSelection )
542 {
543 m_grid->SetGridCursor( row, m_grid->GetGridCursorCol() );
544 firstSelection = false;
545 }
546
547 break;
548 }
549 }
550 }
551}
552
553
555{
556 bool sidebarCollapsed = m_cfgDialogSettings.sidebar_collapsed;
557 int sashPosition = m_cfgDialogSettings.sash_pos;
558 int variantSashPosition = m_cfgDialogSettings.variant_sash_pos;
559
560 m_viewControlsGrid->ShowHideColumns( "0 1 2 3" );
561
562 CallAfter(
563 [this, sidebarCollapsed, sashPosition, variantSashPosition]()
564 {
565 if( sidebarCollapsed )
567 else
568 m_splitterMainWindow->SetSashPosition( sashPosition );
569
570 setSideBarButtonLook( sidebarCollapsed );
571
572 m_splitter_left->SetSashPosition( variantSashPosition );
573 } );
574}
575
576
578{
579 if( !m_cfgDialogSettings.sidebar_collapsed )
580 m_cfgDialogSettings.sash_pos = m_splitterMainWindow->GetSashPosition();
581
582 m_cfgDialogSettings.variant_sash_pos = m_splitter_left->GetSashPosition();
583}
584
585
587{
588 for( int i = 0; i < m_grid->GetNumberCols(); i++ )
589 {
590 if( m_grid->IsColShown( i ) )
591 {
592 std::string fieldName( getDataModel()->GetColFieldName( i ).ToUTF8() );
593 m_cfgDialogSettings.field_widths[fieldName] = m_grid->GetColSize( i );
594 }
595 }
596}
597
598
599// TODO: this probably needs more validation once reference field editing
600// is actually enabled for non-lib-symbol tables
607
608
610{
611 return new GRID_CELL_FPID_EDITOR( this );
612}
613
614
616{
617 m_grid->EnableEditing( !aReadOnly );
618 m_addFieldButton->Enable( !aReadOnly );
619 m_renameFieldButton->Enable( !aReadOnly );
620 m_removeFieldButton->Enable( !aReadOnly );
621 m_buttonApply->Enable( !aReadOnly );
622}
623
624
629
630
632{
633 wxGridCellAttr* attr = new wxGridCellAttr;
634 attr->SetReadOnly( false );
635
636 // Set some column types to specific editors
637 if( getDataModel()->ColIsItemIdentifier( aCol ) )
638 {
639 attr->SetReadOnly();
640 attr->SetRenderer( new GRID_CELL_TEXT_RENDERER() );
641 getDataModel()->SetColAttr( attr, aCol );
642 }
643 else if( getDataModel()->ColIsReference( aCol ) )
644 {
645 // Keep this after item identifiers so Reference remains read-only in tables where it
646 // identifies the item.
647 attr->SetRenderer( new GRID_CELL_TEXT_RENDERER() );
648 attr->SetEditor( createReferenceEditor() );
649 getDataModel()->SetColAttr( attr, aCol );
650 }
651 else if( getDataModel()->GetColFieldName( aCol ) == GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED ) )
652 {
653 attr->SetEditor( createFootprintEditor() );
654 getDataModel()->SetColAttr( attr, aCol );
655 }
656 else if( getDataModel()->GetColFieldName( aCol ) == GetDefaultFieldName( FIELD_T::DATASHEET, UNTRANSLATED ) )
657 {
658 // set datasheet column viewer button
659 attr->SetEditor( createDatasheetEditor() );
660 getDataModel()->SetColAttr( attr, aCol );
661 }
662 else if( getDataModel()->ColIsQuantity( aCol ) || getDataModel()->ColIsItemNumber( aCol ) )
663 {
664 attr->SetReadOnly();
665 attr->SetAlignment( wxALIGN_RIGHT, wxALIGN_CENTER );
666 attr->SetRenderer( new wxGridCellNumberRenderer() );
667 getDataModel()->SetColAttr( attr, aCol );
668 }
669 else if( getDataModel()->ColIsAttribute( aCol ) )
670 {
671 attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
672 attr->SetRenderer( new GRID_CELL_CHECKBOX_RENDERER() );
673 attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
674 getDataModel()->SetColAttr( attr, aCol );
675 }
676 else if( getDataModel()->ColIsReadOnly( aCol ) )
677 {
678 attr->SetReadOnly();
679 getDataModel()->SetColAttr( attr, aCol );
680 }
681 else
682 {
683 attr->SetRenderer( new GRID_CELL_TEXT_RENDERER() );
684 attr->SetEditor( m_grid->GetDefaultEditor() );
685 getDataModel()->SetColAttr( attr, aCol );
686 }
687}
688
689
691{
692 wxSize defaultDlgSize = GetDefaultDialogSize();
693
694 // Restore column sorting order and widths
695 m_grid->AutoSizeColumns( false );
696 int sortCol = 0;
697 bool sortAscending = true;
698
699 for( int col = 0; col < m_grid->GetNumberCols(); ++col )
700 {
702
703 if( col == getDataModel()->GetSortCol() )
704 {
705 sortCol = col;
706 sortAscending = getDataModel()->GetSortAsc();
707 }
708 }
709
710 // sync m_grid's column visibilities to Show checkboxes in m_viewControlsGrid
711 for( int i = 0; i < m_viewControlsDataModel->GetNumberRows(); ++i )
712 {
713 int col = getDataModel()->GetFieldNameCol( m_viewControlsDataModel->GetUntranslatedFieldName( i ) );
714
715 if( col == -1 )
716 continue;
717
718 bool show = m_viewControlsDataModel->GetValueAsBool( i, SHOW_FIELD_COLUMN );
719 getDataModel()->SetShowColumn( col, show );
720
721 if( show )
722 {
723 m_grid->ShowCol( col );
724
725 std::string key( getDataModel()->GetColFieldName( col ).ToUTF8() );
726
727 if( m_cfgDialogSettings.field_widths.count( key ) && ( m_cfgDialogSettings.field_widths.at( key ) > 0 ) )
728 {
729 m_grid->SetColSize( col, m_cfgDialogSettings.field_widths.at( key ) );
730 }
731 else
732 {
733 int textWidth = getDataModel()->GetColDataWidth( col ) + COLUMN_MARGIN;
734 int maxWidth = defaultDlgSize.x / 3;
735
736 m_grid->SetColSize( col, std::clamp( textWidth, 100, maxWidth ) );
737 }
738 }
739 else
740 {
741 m_grid->HideCol( col );
742 }
743 }
744
745 getDataModel()->SetSorting( sortCol, sortAscending );
746 m_grid->SetSortingColumn( sortCol, sortAscending );
747}
748
749
750void DIALOG_FIELDS_TABLE::setSideBarButtonLook( bool aIsLeftPanelCollapsed )
751{
752 // Set bitmap and tooltip according to left panel visibility
753
754 if( aIsLeftPanelCollapsed )
755 {
757 m_sidebarButton->SetToolTip( _( "Expand left panel" ) );
758 }
759 else
760 {
762 m_sidebarButton->SetToolTip( _( "Collapse left panel" ) );
763 }
764}
765
766
767void DIALOG_FIELDS_TABLE::OnSidebarToggle( wxCommandEvent& event )
768{
769 if( m_cfgDialogSettings.sidebar_collapsed )
770 {
771 m_cfgDialogSettings.sidebar_collapsed = false;
773 }
774 else
775 {
776 m_cfgDialogSettings.sash_pos = m_splitterMainWindow->GetSashPosition();
777
778 m_cfgDialogSettings.sidebar_collapsed = true;
780 }
781
782 setSideBarButtonLook( m_cfgDialogSettings.sidebar_collapsed );
783}
784
785
787{
788 if( getDataModel()->IsExpanderColumn( event.GetCol() ) )
789 {
790 m_grid->ClearSelection();
791
792 getDataModel()->ExpandCollapseRow( event.GetRow() );
793 m_grid->SetGridCursor( event.GetRow(), event.GetCol() );
794 }
795 else
796 {
797 event.Skip();
798 }
799}
800
801
803{
804 m_grid->ForceRefresh();
805}
806
807
808void DIALOG_FIELDS_TABLE::OnTableColSize( wxGridSizeEvent& aEvent )
809{
810 aEvent.Skip();
811
812 m_grid->ForceRefresh();
813}
814
815
816void DIALOG_FIELDS_TABLE::OnFilterMouseMoved( wxMouseEvent& aEvent )
817{
818#if defined( __WXOSX__ ) // Doesn't work properly on other ports
819 wxPoint pos = aEvent.GetPosition();
820 wxRect ctrlRect = m_filter->GetScreenRect();
821 int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
822
823 // TODO: restore cursor when mouse leaves the filter field (or is it a MSW bug?)
824 if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
825 SetCursor( wxCURSOR_ARROW );
826 else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
827 SetCursor( wxCURSOR_ARROW );
828 else
829 SetCursor( wxCURSOR_IBEAM );
830#endif
831}
832
833
835{
836 const wxString& showColLabel = m_viewControlsGrid->GetColLabelValue( SHOW_FIELD_COLUMN );
837 const wxString& groupByColLabel = m_viewControlsGrid->GetColLabelValue( GROUP_BY_COLUMN );
838 int showColWidth = KIUI::GetTextSize( showColLabel, m_viewControlsGrid ).x + COLUMN_MARGIN;
839 int groupByColWidth = KIUI::GetTextSize( groupByColLabel, m_viewControlsGrid ).x + COLUMN_MARGIN;
840 int remainingWidth = m_viewControlsGrid->GetSize().GetX() - showColWidth - groupByColWidth;
841
842 m_viewControlsGrid->SetColSize( showColWidth, SHOW_FIELD_COLUMN );
843 m_viewControlsGrid->SetColSize( groupByColWidth, GROUP_BY_COLUMN );
844
845 if( m_viewControlsGrid->IsColShown( DISPLAY_NAME_COLUMN ) && m_viewControlsGrid->IsColShown( LABEL_COLUMN ) )
846 {
847 m_viewControlsGrid->SetColSize( DISPLAY_NAME_COLUMN, std::max( remainingWidth / 2, 60 ) );
848 m_viewControlsGrid->SetColSize( LABEL_COLUMN, std::max( remainingWidth - ( remainingWidth / 2 ), 60 ) );
849 }
850 else if( m_viewControlsGrid->IsColShown( DISPLAY_NAME_COLUMN ) )
851 {
852 m_viewControlsGrid->SetColSize( DISPLAY_NAME_COLUMN, std::max( remainingWidth, 60 ) );
853 }
854 else if( m_viewControlsGrid->IsColShown( LABEL_COLUMN ) )
855 {
856 m_viewControlsGrid->SetColSize( LABEL_COLUMN, std::max( remainingWidth, 60 ) );
857 }
858
859 event.Skip();
860}
861
862
864{
865 int row = aEvent.GetRow();
866
867 wxCHECK( row < m_viewControlsGrid->GetNumberRows(), /* void */ );
868
869 switch( aEvent.GetCol() )
870 {
871 case LABEL_COLUMN:
872 {
873 wxString label = m_viewControlsDataModel->GetValue( row, LABEL_COLUMN );
874 wxString fieldName = m_viewControlsDataModel->GetUntranslatedFieldName( row );
875 int dataCol = getDataModel()->GetFieldNameCol( fieldName );
876
877 if( dataCol != -1 )
878 {
879 getDataModel()->SetColLabelValue( dataCol, label );
880 m_grid->SetColLabelValue( dataCol, label );
881
882 if( m_nbPages->GetSelection() == 1 )
884 else
885 m_grid->ForceRefresh();
886
888 OnModify();
889 }
890
891 break;
892 }
893
895 {
896 wxString fieldName = m_viewControlsDataModel->GetUntranslatedFieldName( row );
897 bool value = m_viewControlsDataModel->GetValueAsBool( row, SHOW_FIELD_COLUMN );
898 int dataCol = getDataModel()->GetFieldNameCol( fieldName );
899
900 if( dataCol != -1 )
901 ShowHideColumn( dataCol, value );
902
903 break;
904 }
905
906 case GROUP_BY_COLUMN:
907 {
908 wxString fieldName = m_viewControlsDataModel->GetUntranslatedFieldName( row );
909 bool value = m_viewControlsDataModel->GetValueAsBool( row, GROUP_BY_COLUMN );
910 int dataCol = getDataModel()->GetFieldNameCol( fieldName );
911
912 if( getDataModel()->ColIsQuantity( dataCol ) && value )
913 {
914 DisplayError( this, _( "The Quantity column cannot be grouped by." ) );
915
916 value = false;
917 m_viewControlsDataModel->SetValueAsBool( row, GROUP_BY_COLUMN, value );
918 m_viewControlsGrid->ForceRefresh();
919 break;
920 }
921
922 if( getDataModel()->ColIsItemNumber( dataCol ) && value )
923 {
924 DisplayError( this, _( "The Item Number column cannot be grouped by." ) );
925
926 value = false;
927 m_viewControlsDataModel->SetValueAsBool( row, GROUP_BY_COLUMN, value );
928 m_viewControlsGrid->ForceRefresh();
929 break;
930 }
931
932 getDataModel()->SetGroupColumn( dataCol, value );
934
935 if( m_nbPages->GetSelection() == 1 )
937 else
938 m_grid->ForceRefresh();
939
941 OnModify();
942 break;
943 }
944
945 default: break;
946 }
947}
948
949
950void DIALOG_FIELDS_TABLE::OnAddField( wxCommandEvent& aEvent )
951{
952 wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Add Field" ) );
953
954 if( dlg.ShowModal() != wxID_OK )
955 return;
956
957 wxString fieldName = dlg.GetValue();
958
959 if( fieldName.IsEmpty() )
960 {
961 DisplayError( this, _( "Field must have a name." ) );
962 return;
963 }
964
965 for( int i = 0; i < getDataModel()->GetNumberCols(); ++i )
966 {
967 if( FieldNamesAreDuplicates( fieldName, getDataModel()->GetColFieldName( i ) ) )
968 {
969 DisplayError( this, wxString::Format( _( "Field name '%s' already in use." ), fieldName ) );
970 return;
971 }
972 }
973
974 AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), true, false, true );
975
976 SetupColumnProperties( getDataModel()->GetColsCount() - 1 );
977
979 OnModify();
980}
981
982
983void DIALOG_FIELDS_TABLE::OnRemoveField( wxCommandEvent& aEvent )
984{
985 m_viewControlsGrid->OnDeleteRows(
986 [&]( int row )
987 {
988 for( FIELD_T id : MANDATORY_FIELDS )
989 {
990 if( m_mandatoryFieldListIndexes[id] == row )
991 {
992 DisplayError( this, wxString::Format( _( "The first %d fields are mandatory." ),
993 (int) m_mandatoryFieldListIndexes.size() ) );
994 return false;
995 }
996 }
997
998 return IsOK( this, wxString::Format( _( "Are you sure you want to remove the field '%s'?" ),
999 m_viewControlsDataModel->GetValue( row, DISPLAY_NAME_COLUMN ) ) );
1000 },
1001 [&]( int row )
1002 {
1003 wxString fieldName = m_viewControlsDataModel->GetUntranslatedFieldName( row );
1004 int col = getDataModel()->GetFieldNameCol( fieldName );
1005
1006 if( col != -1 )
1007 getDataModel()->RemoveColumn( col );
1008
1009 m_viewControlsDataModel->DeleteRow( row );
1010
1012 OnModify();
1013 } );
1014}
1015
1016
1017void DIALOG_FIELDS_TABLE::OnRenameField( wxCommandEvent& aEvent )
1018{
1019 wxArrayInt selectedRows = m_viewControlsGrid->GetSelectedRows();
1020
1021 if( selectedRows.empty() && m_viewControlsGrid->GetGridCursorRow() >= 0 )
1022 selectedRows.push_back( m_viewControlsGrid->GetGridCursorRow() );
1023
1024 if( selectedRows.empty() )
1025 return;
1026
1027 int row = selectedRows[0];
1028
1029 for( FIELD_T id : MANDATORY_FIELDS )
1030 {
1031 if( m_mandatoryFieldListIndexes[id] == row )
1032 {
1033 DisplayError( this, wxString::Format( _( "The first %d fields are mandatory and names cannot be changed." ),
1034 (int) m_mandatoryFieldListIndexes.size() ) );
1035 return;
1036 }
1037 }
1038
1039 wxString fieldName = m_viewControlsDataModel->GetUntranslatedFieldName( row );
1040 wxString label = m_viewControlsDataModel->GetValue( row, LABEL_COLUMN );
1041 bool labelIsAutogenerated = label.IsSameAs( GetGeneratedFieldDisplayName( fieldName ) );
1042
1043 int col = getDataModel()->GetFieldNameCol( fieldName );
1044 wxCHECK_RET( col != -1, wxS( "Existing field name missing from data model" ) );
1045
1046 wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Rename Field" ), fieldName );
1047
1048 if( dlg.ShowModal() != wxID_OK )
1049 return;
1050
1051 wxString newFieldName = dlg.GetValue();
1052
1053 // No change, no-op
1054 if( newFieldName == fieldName )
1055 return;
1056
1057 // New field name already exists
1058 if( getDataModel()->GetFieldNameCol( newFieldName ) != -1 )
1059 {
1060 wxString confirm_msg = wxString::Format( _( "Field name %s already exists." ), newFieldName );
1061 DisplayError( this, confirm_msg );
1062 return;
1063 }
1064
1065 getDataModel()->RenameColumn( col, newFieldName );
1066 m_viewControlsDataModel->SetUntranslatedFieldName( row, newFieldName );
1067 m_viewControlsDataModel->SetValue( row, DISPLAY_NAME_COLUMN, newFieldName );
1068
1069 if( labelIsAutogenerated )
1070 {
1071 m_viewControlsDataModel->SetValue( row, LABEL_COLUMN, GetGeneratedFieldDisplayName( newFieldName ) );
1072 wxGridEvent evt( m_viewControlsGrid->GetId(), wxEVT_GRID_CELL_CHANGED, m_viewControlsGrid, row, LABEL_COLUMN );
1074 }
1075
1076 if( m_nbPages->GetSelection() == 1 )
1078 else
1079 {
1081 m_grid->ForceRefresh();
1082 }
1083
1085 OnModify();
1086}
1087
1088
1089void DIALOG_FIELDS_TABLE::OnFilterText( wxCommandEvent& aEvent )
1090{
1091 getDataModel()->SetFilter( m_filter->GetValue() );
1093 m_grid->ForceRefresh();
1094
1096}
1097
1098
1099void DIALOG_FIELDS_TABLE::OnFilterScope( wxCommandEvent& aEvent )
1100{
1101 getDataModel()->SetFilterScope( static_cast<BOM_FILTER_SCOPE>( m_filterScope->GetSelection() ) );
1103 m_grid->ForceRefresh();
1104
1106}
1107
1108
1110{
1113 m_grid->ForceRefresh();
1114
1116}
1117
1118
1119void DIALOG_FIELDS_TABLE::OnRegroupSymbols( wxCommandEvent& aEvent )
1120{
1122 m_grid->ForceRefresh();
1123}
1124
1125
1126void DIALOG_FIELDS_TABLE::OnColSort( wxGridEvent& aEvent )
1127{
1128 int sortCol = aEvent.GetCol();
1129 bool ascending;
1130
1131 // Don't sort by item number, it is generated by the sort
1132 if( getDataModel()->ColIsItemNumber( sortCol ) )
1133 {
1134 aEvent.Veto();
1135 return;
1136 }
1137
1138 // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the event, and
1139 // if we ask it will give us pre-event info.
1140 if( m_grid->IsSortingBy( sortCol ) )
1141 {
1142 // same column; invert ascending
1143 ascending = !m_grid->IsSortOrderAscending();
1144 }
1145 else
1146 {
1147 // different column; start with ascending
1148 ascending = true;
1149 }
1150
1151 getDataModel()->SetSorting( sortCol, ascending );
1153 m_grid->ForceRefresh();
1154
1156}
1157
1158
1159void DIALOG_FIELDS_TABLE::OnColMove( wxGridEvent& aEvent )
1160{
1161 int origPos = aEvent.GetCol();
1162
1163 // Save column widths since the setup function uses the saved config values
1165
1166 CallAfter(
1167 [origPos, this]()
1168 {
1169 int newPos = m_grid->GetColPos( origPos );
1170
1171#ifdef __WXMAC__
1172 if( newPos < origPos )
1173 newPos += 1;
1174#endif
1175
1176 getDataModel()->MoveColumn( origPos, newPos );
1177
1178 // "Unmove" the column since we've moved the column internally
1179 m_grid->ResetColPos();
1180
1181 // We need to reset all the column attr's to the correct column order
1183
1184 m_grid->ForceRefresh();
1185 } );
1186
1188}
1189
1190
1191void DIALOG_FIELDS_TABLE::OnGridMouseMove( wxMouseEvent& aEvent )
1192{
1193 aEvent.Skip();
1194
1195 wxPoint pos = aEvent.GetPosition();
1196 int ux, uy;
1197 m_grid->CalcUnscrolledPosition( pos.x, pos.y, &ux, &uy );
1198 int row = m_grid->YToRow( uy );
1199 int col = m_grid->XToCol( ux );
1200
1201 if( row == wxNOT_FOUND || col == wxNOT_FOUND )
1202 {
1203 m_grid->GetGridWindow()->UnsetToolTip();
1204 return;
1205 }
1206
1207 wxString rawValue = getDataModel()->GetValue( row, col );
1208
1209 if( rawValue.Contains( wxT( "${" ) ) )
1210 m_grid->GetGridWindow()->SetToolTip( rawValue );
1211 else
1212 m_grid->GetGridWindow()->UnsetToolTip();
1213}
1214
1215
1216void DIALOG_FIELDS_TABLE::OnGridMouseWheel( wxMouseEvent& aEvent )
1217{
1218 if( aEvent.GetWheelAxis() != wxMOUSE_WHEEL_VERTICAL || aEvent.GetModifiers() != wxMOD_SHIFT )
1219 {
1220 aEvent.Skip();
1221 return;
1222 }
1223
1224 // Accumulate so high-resolution wheels and trackpads aren't truncated.
1225 m_gridWheelRotation += aEvent.GetWheelRotation();
1226
1227 int lines = m_gridWheelRotation / aEvent.GetWheelDelta();
1228 m_gridWheelRotation -= lines * aEvent.GetWheelDelta();
1229
1230 if( lines == 0 )
1231 return;
1232
1233 int scrollLines = aEvent.IsPageScroll() ? m_grid->GetScrollPageSize( wxHORIZONTAL )
1234 : aEvent.GetLinesPerAction();
1235
1236 wxPoint viewStart = m_grid->GetViewStart();
1237 m_grid->Scroll( viewStart.x - lines * scrollLines, viewStart.y );
1238}
1239
1240
1241void DIALOG_FIELDS_TABLE::OnPageChanged( wxNotebookEvent& aEvent )
1242{
1243 if( getDataModel()->GetColsCount() )
1245}
1246
1247
1248void DIALOG_FIELDS_TABLE::OnPreviewRefresh( wxCommandEvent& aEvent )
1249{
1252}
1253
1254
1256{
1257 // Build the absolute path of current output directory to preselect it in the file browser.
1258 wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() );
1259 path = Prj().AbsolutePath( path );
1260
1261 // Calculate the export filename
1262 wxFileName fn( Prj().AbsolutePath( m_parentFrame->GetCurrentFileName() ) );
1263 fn.SetExt( FILEEXT::CsvFileExtension );
1264
1265 wxFileDialog saveDlg( this, _( "Bill of Materials Output File" ), path, fn.GetFullName(),
1266 FILEEXT::CsvFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
1267
1269
1270 if( saveDlg.ShowModal() == wxID_CANCEL )
1271 return;
1272
1273 wxFileName file = wxFileName( saveDlg.GetPath() );
1274 wxString defaultPath = fn.GetPathWithSep();
1275
1276 if( IsOK( this, wxString::Format( _( "Do you want to use a path relative to\n'%s'?" ), defaultPath ) ) )
1277 {
1278 if( !file.MakeRelativeTo( defaultPath ) )
1279 {
1280 DisplayErrorMessage( this, _( "Cannot make path relative (target volume different from file "
1281 "volume)!" ) );
1282 }
1283 }
1284
1285 m_outputFileName->SetValue( file.GetFullPath() );
1286}
1287
1288
1289void DIALOG_FIELDS_TABLE::OnExport( wxCommandEvent& aEvent )
1290{
1291 if( getDataModel()->IsEdited() )
1292 {
1293 if( OKOrCancelDialog( nullptr, _( "Unsaved data" ),
1294 _( "Changes have not yet been saved. Export unsaved data?" ), "", _( "OK" ),
1295 _( "Cancel" ) )
1296 == wxID_CANCEL )
1297 {
1298 return;
1299 }
1300 }
1301
1302 // Create output directory if it does not exist (also transform it in absolute form).
1303 // Bail if it fails.
1304
1305 std::function<bool( wxString* )> textResolver =
1306 [&]( wxString* token ) -> bool
1307 {
1308 return resolveTextVar( token );
1309 };
1310
1311 wxString sourceFileName = m_parentFrame->GetCurrentFileName();
1312 wxString path = m_outputFileName->GetValue();
1313
1314 if( path.IsEmpty() )
1315 {
1316 // Match the behaviour of other exporters and default to the source filename with a CSV
1317 // extension in the project directory when the user leaves the field blank.
1318 path = GetDefaultBomFileName( sourceFileName );
1319
1320 if( path.IsEmpty() )
1321 {
1322 DisplayError( this, _( "No output file specified in Export tab." ) );
1323 return;
1324 }
1325
1326 m_outputFileName->SetValue( path );
1327 }
1328
1331
1332 wxFileName outputFile = wxFileName::FileName( path );
1333 wxString msg;
1334
1335 if( !EnsureFileDirectoryExists( &outputFile, Prj().AbsolutePath( sourceFileName ), &NULL_REPORTER::GetInstance() ) )
1336 {
1337 msg.Printf( _( "Could not open/create path '%s'." ), outputFile.GetPath() );
1338 DisplayError( this, msg );
1339 return;
1340 }
1341
1342 wxFFile out( outputFile.GetFullPath(), "wb" );
1343
1344 if( !out.IsOpened() )
1345 {
1346 msg.Printf( _( "Could not create BOM output '%s'." ), outputFile.GetFullPath() );
1347 DisplayError( this, msg );
1348 return;
1349 }
1350
1352
1353 if( !out.Write( m_textOutput->GetValue() ) )
1354 {
1355 msg.Printf( _( "Could not write BOM output '%s'." ), outputFile.GetFullPath() );
1356 DisplayError( this, msg );
1357 return;
1358 }
1359
1360 // close the file before we tell the user it's done with the info modal :workflow meme:
1361 out.Close();
1362
1363 if( m_cfgBomSettings.m_BomExportFileName != m_outputFileName->GetValue() )
1364 {
1365 m_cfgBomSettings.m_BomExportFileName = m_outputFileName->GetValue();
1367 }
1368
1369 msg.Printf( _( "Wrote BOM output to '%s'" ), outputFile.GetFullPath() );
1370 DisplayInfoMessage( this, msg );
1371}
1372
1373
1375{
1376 bool saveIncludeExcudedFromBOM = getDataModel()->GetIncludeExcludedFromBOM();
1377
1380
1381 m_textOutput->SetValue( getDataModel()->Export( GetCurrentBomFmtSettings() ) );
1382
1383 if( saveIncludeExcudedFromBOM )
1384 {
1387 }
1388}
1389
1390
1392{
1393 wxString retv;
1394
1395 int selection = m_variantListBox->GetSelection();
1396
1397 if( ( selection == wxNOT_FOUND ) || ( m_variantListBox->GetString( selection ) == GetDefaultVariantName() ) )
1398 return retv;
1399
1400 return m_variantListBox->GetString( selection );
1401}
1402
1403
1404std::vector<BOM_PRESET> DIALOG_FIELDS_TABLE::GetUserBomPresets() const
1405{
1406 std::vector<BOM_PRESET> ret;
1407
1408 for( const std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
1409 {
1410 if( !pair.second.readOnly )
1411 ret.emplace_back( pair.second );
1412 }
1413
1414 return ret;
1415}
1416
1417
1418void DIALOG_FIELDS_TABLE::SetUserBomPresets( std::vector<BOM_PRESET>& aPresetList )
1419{
1420 // Reset to defaults
1422
1423 for( const BOM_PRESET& preset : aPresetList )
1424 {
1425 if( m_bomPresets.count( preset.name ) )
1426 continue;
1427
1428 m_bomPresets[preset.name] = preset;
1429
1430 m_bomPresetMRU.Add( preset.name );
1431 }
1432
1434}
1435
1436
1437void DIALOG_FIELDS_TABLE::ApplyBomPreset( const wxString& aPresetName )
1438{
1439 updateBomPresetSelection( aPresetName );
1440
1441 wxCommandEvent dummy;
1443}
1444
1445
1446void DIALOG_FIELDS_TABLE::ApplyBomPreset( const BOM_PRESET& aPreset )
1447{
1448 BOM_PRESET preset = aPreset;
1449
1450 if( preset.fieldsOrdered.empty() )
1451 {
1452 for( const BOM_PRESET& builtIn : getBuiltInBomPresets() )
1453 {
1454 if( builtIn.name == BOM_PRESET::DefaultEditing().name )
1455 {
1456 preset = builtIn;
1457 break;
1458 }
1459 }
1460 }
1461
1462 if( m_bomPresets.count( preset.name ) )
1463 m_currentBomPreset = &m_bomPresets[preset.name];
1464 else
1465 m_currentBomPreset = nullptr;
1466
1467 if( m_currentBomPreset && !m_currentBomPreset->readOnly )
1469 else
1470 m_lastSelectedBomPreset = nullptr;
1471
1472 updateBomPresetSelection( preset.name );
1473 doApplyBomPreset( preset );
1474}
1475
1476
1477void DIALOG_FIELDS_TABLE::doApplyBomPreset( const BOM_PRESET& aPreset )
1478{
1479 // Disable rebuilds while we're applying the preset otherwise we'll be
1480 // rebuilding the model constantly while firing off wx events
1482
1483 // Basically, we apply the BOM preset to the data model and then
1484 // update our UI to reflect resulting the data model state, not the preset.
1486 getDataModel()->ApplyBomPreset( aPreset );
1487
1488 // BOM Presets can add, but not remove, columns, so make sure the view controls
1489 // grid has all of them before starting
1490 for( int i = 0; i < getDataModel()->GetColsCount(); i++ )
1491 {
1492 const wxString& fieldName( getDataModel()->GetColFieldName( i ) );
1493 bool found = false;
1494
1495 for( int j = 0; j < m_viewControlsDataModel->GetNumberRows(); j++ )
1496 {
1497 if( m_viewControlsDataModel->GetUntranslatedFieldName( j ) == fieldName )
1498 {
1499 found = true;
1500 break;
1501 }
1502 }
1503
1504 // Properties like label, etc. will be added in the next loop
1505 if( !found )
1506 AddField( fieldName, GetGeneratedFieldDisplayName( fieldName ), false, false );
1507 }
1508
1509 // Sync all fields
1510 for( int i = 0; i < m_viewControlsDataModel->GetNumberRows(); i++ )
1511 {
1512 const wxString& fieldName( m_viewControlsDataModel->GetUntranslatedFieldName( i ) );
1513 int col = getDataModel()->GetFieldNameCol( fieldName );
1514
1515 if( col == -1 )
1516 {
1517 wxASSERT_MSG( true, "Fields control has a field not found in the data model." );
1518 continue;
1519 }
1520
1521 std::string fieldNameStr( fieldName.ToUTF8() );
1522
1523 // Set column labels
1524 const wxString& label = getDataModel()->GetColLabelValue( col );
1525 m_viewControlsDataModel->SetValue( i, LABEL_COLUMN, label );
1526 m_grid->SetColLabelValue( col, label );
1527
1528 if( m_cfgDialogSettings.field_widths.count( fieldNameStr ) )
1529 m_grid->SetColSize( col, m_cfgDialogSettings.field_widths.at( fieldNameStr ) );
1530
1531 // Set shown columns
1532 bool show = getDataModel()->GetShowColumn( col );
1533 m_viewControlsDataModel->SetValueAsBool( i, SHOW_FIELD_COLUMN, show );
1534
1535 if( show )
1536 m_grid->ShowCol( col );
1537 else
1538 m_grid->HideCol( col );
1539
1540 // Set grouped columns
1541 bool groupBy = getDataModel()->GetGroupColumn( col );
1542 m_viewControlsDataModel->SetValueAsBool( i, GROUP_BY_COLUMN, groupBy );
1543 }
1544
1545 m_grid->SetSortingColumn( getDataModel()->GetSortCol(), getDataModel()->GetSortAsc() );
1546 m_groupSymbolsBox->SetValue( getDataModel()->GetGroupingEnabled() );
1547 m_filter->ChangeValue( getDataModel()->GetFilter() );
1548 m_filterScope->SetSelection( static_cast<int>( getDataModel()->GetFilterScope() ) );
1549
1550 // Rebuild before sizing columns so their widths account for the current row data.
1553
1555
1556 if( m_nbPages->GetSelection() == 1 )
1558 else
1559 m_grid->ForceRefresh();
1560}
1561
1562
1564{
1565 m_bomPresets.clear();
1566 m_bomPresetMRU.clear();
1567
1568 // Load the read-only defaults
1569 for( const BOM_PRESET& preset : getBuiltInBomPresets() )
1570 {
1571 m_bomPresets[preset.name] = preset;
1572 m_bomPresets[preset.name].readOnly = true;
1573
1574 m_bomPresetMRU.Add( preset.name );
1575 }
1576}
1577
1578
1579std::vector<BOM_PRESET> DIALOG_FIELDS_TABLE::getBuiltInBomPresets() const
1580{
1581 return BOM_PRESET::BuiltInPresets();
1582}
1583
1584
1586{
1587 m_cbBomPresets->Clear();
1588
1589 int idx = 0;
1590 int default_idx = 0;
1591
1592 for( const auto& [presetName, preset] : m_bomPresets )
1593 {
1594 m_cbBomPresets->Append( wxGetTranslation( presetName ), (void*) &preset );
1595
1596 if( presetName == BOM_PRESET::DefaultEditing().name )
1597 default_idx = idx;
1598
1599 idx++;
1600 }
1601
1602 m_cbBomPresets->Append( wxT( "---" ) );
1603
1604 if( !m_job )
1605 {
1606 m_cbBomPresets->Append( _( "Save preset..." ) );
1607 m_cbBomPresets->Append( _( "Delete preset..." ) );
1608 }
1609
1610 // At least the built-in presets should always be present
1611 wxASSERT( !m_bomPresets.empty() );
1612
1613 m_cbBomPresets->SetSelection( default_idx );
1614 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( default_idx ) );
1615}
1616
1617
1619{
1620 return aPresetCount - ( m_job ? 1 : 3 );
1621}
1622
1623
1625{
1626 return m_job ? wxNOT_FOUND : aPresetCount - 2;
1627}
1628
1629
1631{
1632 return m_job ? wxNOT_FOUND : aPresetCount - 1;
1633}
1634
1635
1640
1641
1643{
1644 BOM_PRESET current = getDataModelBomPreset();
1645
1646 auto it = std::find_if( m_bomPresets.begin(), m_bomPresets.end(),
1647 [&]( const std::pair<const wxString, BOM_PRESET>& aPair )
1648 {
1649 const BOM_PRESET& preset = aPair.second;
1650
1651 // Check the simple settings first
1652 if( !( preset.sortAsc == current.sortAsc
1653 && preset.filterString == current.filterString
1654 && preset.filterScope == current.filterScope
1655 && preset.groupSymbols == current.groupSymbols
1656 && preset.excludeDNP == current.excludeDNP
1657 && preset.includeExcludedFromBOM == current.includeExcludedFromBOM ) )
1658 {
1659 return false;
1660 }
1661
1662 // We should compare preset.name and current.name. Unfortunately current.name is
1663 // empty because m_dataModel->GetBomSettings() does not store the .name member.
1664 // So use sortField member as a (not very efficient) auxiliary filter.
1665 // As a further complication, sortField can be translated in m_bomPresets list, so
1666 // current.sortField needs to be translated.
1667 // Probably this not efficient and error prone test should be removed (JPC).
1668 if( preset.sortField != wxGetTranslation( current.sortField ) )
1669 return false;
1670
1671 // Only compare shown or grouped fields
1672 std::vector<BOM_FIELD> A, B;
1673
1674 for( const BOM_FIELD& field : preset.fieldsOrdered )
1675 {
1676 if( field.show || field.groupBy )
1677 A.emplace_back( field );
1678 }
1679
1680 for( const BOM_FIELD& field : current.fieldsOrdered )
1681 {
1682 if( field.show || field.groupBy )
1683 B.emplace_back( field );
1684 }
1685
1686 return A == B;
1687 } );
1688
1689 if( it != m_bomPresets.end() )
1690 {
1691 // Select the right m_cbBomPresets item.
1692 // but these items are translated if they are predefined items.
1693 bool do_translate = it->second.readOnly;
1694 wxString text = do_translate ? wxGetTranslation( it->first ) : it->first;
1695 m_cbBomPresets->SetStringSelection( text );
1696 }
1697 else
1698 {
1699 m_cbBomPresets->SetSelection( presetDashDashDashIndex( m_cbBomPresets->GetCount() ) );
1700 }
1701
1702 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( m_cbBomPresets->GetSelection() ) );
1703}
1704
1705
1707{
1708 // Look at m_userBomPresets to know if aName is a read only preset, or a user preset.
1709 // Read-only presets have translated names in UI, so we have to use a translated name
1710 // in UI selection. But for a user preset name we search for the untranslated aName.
1711 wxString ui_label = aName;
1712
1713 for( const auto& [presetName, preset] : m_bomPresets )
1714 {
1715 if( presetName == aName )
1716 {
1717 if( preset.readOnly == true )
1718 ui_label = wxGetTranslation( aName );
1719
1720 break;
1721 }
1722 }
1723
1724 int idx = m_cbBomPresets->FindString( ui_label );
1725
1726 if( idx >= 0 && m_cbBomPresets->GetSelection() != idx )
1727 {
1728 m_cbBomPresets->SetSelection( idx );
1729 m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( idx ) );
1730 }
1731 else if( idx < 0 )
1732 {
1733 m_cbBomPresets->SetSelection( presetDashDashDashIndex( m_cbBomPresets->GetCount() ) );
1734 }
1735}
1736
1737
1738void DIALOG_FIELDS_TABLE::onBomPresetChanged( wxCommandEvent& aEvent )
1739{
1740 int count = m_cbBomPresets->GetCount();
1741 int index = m_cbBomPresets->GetSelection();
1742
1743 auto resetSelection =
1744 [&]()
1745 {
1746 if( m_currentBomPreset )
1747 m_cbBomPresets->SetStringSelection( m_currentBomPreset->name );
1748 else
1749 m_cbBomPresets->SetSelection( presetDashDashDashIndex( m_cbBomPresets->GetCount() ) );
1750 };
1751
1752 if( index == presetDashDashDashIndex( count ) )
1753 {
1754 // Separator: reject the selection
1755 resetSelection();
1756 return;
1757 }
1758 else if( index == presetSavePresetIndex( count ) )
1759 {
1760 // Save current state to new preset
1761 wxString name;
1762
1765
1766 wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
1767
1768 if( dlg.ShowModal() != wxID_OK )
1769 {
1770 resetSelection();
1771 return;
1772 }
1773
1774 name = dlg.GetValue();
1775 bool exists = m_bomPresets.count( name );
1776
1777 if( !exists )
1778 {
1780 m_bomPresets[name].readOnly = false;
1781 m_bomPresets[name].name = name;
1782 }
1783
1784 BOM_PRESET* preset = &m_bomPresets[name];
1785
1786 if( !exists )
1787 {
1788 index = m_cbBomPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
1789 }
1790 else if( preset->readOnly )
1791 {
1792 wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
1793 _( "Error" ), wxOK | wxICON_ERROR, this );
1794 resetSelection();
1795 return;
1796 }
1797 else
1798 {
1799 // Ask the user if they want to overwrite the existing preset
1800 if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
1801 {
1802 resetSelection();
1803 return;
1804 }
1805
1806 *preset = getDataModelBomPreset();
1807 preset->name = name;
1808
1809 index = m_cbBomPresets->FindString( name );
1810
1811 if( m_bomPresetMRU.Index( name ) != wxNOT_FOUND )
1812 m_bomPresetMRU.Remove( name );
1813 }
1814
1815 m_currentBomPreset = preset;
1816 m_cbBomPresets->SetSelection( index );
1817 m_bomPresetMRU.Insert( name, 0 );
1818
1819 return;
1820 }
1821 else if( index == presetDeletePresetIndex( count ) )
1822 {
1823 // Delete a preset
1824 wxArrayString headers;
1825 std::vector<wxArrayString> items;
1826
1827 headers.Add( _( "Presets" ) );
1828
1829 for( const auto& [name, preset] : m_bomPresets )
1830 {
1831 if( !preset.readOnly )
1832 {
1833 wxArrayString item;
1834 item.Add( name );
1835 items.emplace_back( item );
1836 }
1837 }
1838
1839 EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
1840 dlg.SetListLabel( _( "Select preset:" ) );
1841
1842 if( dlg.ShowModal() == wxID_OK )
1843 {
1844 wxString presetName = dlg.GetTextSelection();
1845 int idx = m_cbBomPresets->FindString( presetName );
1846
1847 if( idx != wxNOT_FOUND )
1848 {
1849 m_bomPresets.erase( presetName );
1850
1851 m_cbBomPresets->Delete( idx );
1852 m_currentBomPreset = nullptr;
1853 }
1854
1855 if( m_bomPresetMRU.Index( presetName ) != wxNOT_FOUND )
1856 m_bomPresetMRU.Remove( presetName );
1857 }
1858
1859 resetSelection();
1860 return;
1861 }
1862
1863 BOM_PRESET* preset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( index ) );
1864 m_currentBomPreset = preset;
1865
1866 m_lastSelectedBomPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
1867
1868 if( preset )
1869 {
1870 doApplyBomPreset( *preset );
1872 m_currentBomPreset = preset;
1873
1874 if( !m_currentBomPreset->name.IsEmpty() )
1875 {
1876 if( m_bomPresetMRU.Index( preset->name ) != wxNOT_FOUND )
1877 m_bomPresetMRU.Remove( preset->name );
1878
1879 m_bomPresetMRU.Insert( preset->name, 0 );
1880 }
1881 }
1882}
1883
1884
1886{
1887 BOM_FMT_PRESET current;
1888
1889 current.name = m_cbBomFmtPresets->GetStringSelection();
1890 current.fieldDelimiter = m_textFieldDelimiter->GetValue();
1891 current.stringDelimiter = m_textStringDelimiter->GetValue();
1892 current.refDelimiter = m_textRefDelimiter->GetValue();
1893 current.refRangeDelimiter = m_textRefRangeDelimiter->GetValue();
1894 current.keepTabs = m_checkKeepTabs->GetValue();
1895 current.keepLineBreaks = m_checkKeepLineBreaks->GetValue();
1897
1898 return current;
1899}
1900
1901
1902std::vector<BOM_FMT_PRESET> DIALOG_FIELDS_TABLE::GetUserBomFmtPresets() const
1903{
1904 std::vector<BOM_FMT_PRESET> ret;
1905
1906 for( const auto& [name, preset] : m_bomFmtPresets )
1907 {
1908 if( !preset.readOnly )
1909 ret.emplace_back( preset );
1910 }
1911
1912 return ret;
1913}
1914
1915
1916void DIALOG_FIELDS_TABLE::SetUserBomFmtPresets( std::vector<BOM_FMT_PRESET>& aPresetList )
1917{
1918 // Reset to defaults
1920
1921 for( const BOM_FMT_PRESET& preset : aPresetList )
1922 {
1923 if( m_bomFmtPresets.count( preset.name ) )
1924 continue;
1925
1926 m_bomFmtPresets[preset.name] = preset;
1927
1928 m_bomFmtPresetMRU.Add( preset.name );
1929 }
1930
1932}
1933
1934
1935void DIALOG_FIELDS_TABLE::ApplyBomFmtPreset( const wxString& aPresetName )
1936{
1937 updateBomFmtPresetSelection( aPresetName );
1938
1939 wxCommandEvent dummy;
1941}
1942
1943
1945{
1946 m_currentBomFmtPreset = nullptr;
1948
1949 if( m_bomFmtPresets.count( aPreset.name ) )
1951
1954
1956 doApplyBomFmtPreset( aPreset );
1957}
1958
1959
1961{
1962 m_textFieldDelimiter->ChangeValue( aPreset.fieldDelimiter );
1963 m_textStringDelimiter->ChangeValue( aPreset.stringDelimiter );
1964 m_textRefDelimiter->ChangeValue( aPreset.refDelimiter );
1965 m_textRefRangeDelimiter->ChangeValue( aPreset.refRangeDelimiter );
1966 m_checkKeepTabs->SetValue( aPreset.keepTabs );
1967 m_checkKeepLineBreaks->SetValue( aPreset.keepLineBreaks );
1969
1970 // Refresh the preview if that's the current page
1971 if( m_nbPages->GetSelection() == 1 )
1973}
1974
1975
1977{
1978 m_bomFmtPresets.clear();
1979 m_bomFmtPresetMRU.clear();
1980
1981 // Load the read-only defaults
1982 for( const BOM_FMT_PRESET& preset : BOM_FMT_PRESET::BuiltInPresets() )
1983 {
1984 m_bomFmtPresets[preset.name] = preset;
1985 m_bomFmtPresets[preset.name].readOnly = true;
1986
1987 m_bomFmtPresetMRU.Add( preset.name );
1988 }
1989}
1990
1991
1993{
1994 m_cbBomFmtPresets->Clear();
1995
1996 int idx = 0;
1997 int default_idx = 0;
1998
1999 for( const auto& [presetName, preset] : m_bomFmtPresets )
2000 {
2001 m_cbBomFmtPresets->Append( wxGetTranslation( presetName ), (void*) &preset );
2002
2003 if( presetName == BOM_FMT_PRESET::CSV().name )
2004 default_idx = idx;
2005
2006 idx++;
2007 }
2008
2009 m_cbBomFmtPresets->Append( wxT( "---" ) );
2010
2011 if( !m_job )
2012 {
2013 m_cbBomFmtPresets->Append( _( "Save preset..." ) );
2014 m_cbBomFmtPresets->Append( _( "Delete preset..." ) );
2015 }
2016
2017 // At least the built-in presets should always be present
2018 wxASSERT( !m_bomFmtPresets.empty() );
2019
2020 m_cbBomFmtPresets->SetSelection( default_idx );
2021 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( default_idx ) );
2022}
2023
2024
2026{
2028
2029 auto it = std::find_if( m_bomFmtPresets.begin(), m_bomFmtPresets.end(),
2030 [&]( const std::pair<const wxString, BOM_FMT_PRESET>& aPair )
2031 {
2032 return ( aPair.second.fieldDelimiter == current.fieldDelimiter
2033 && aPair.second.stringDelimiter == current.stringDelimiter
2034 && aPair.second.refDelimiter == current.refDelimiter
2035 && aPair.second.refRangeDelimiter == current.refRangeDelimiter
2036 && aPair.second.keepTabs == current.keepTabs
2037 && aPair.second.keepLineBreaks == current.keepLineBreaks
2038 && aPair.second.includeByteOrderMark == current.includeByteOrderMark );
2039 } );
2040
2041 if( it != m_bomFmtPresets.end() )
2042 {
2043 // Select the right m_cbBomFmtPresets item.
2044 // but these items are translated if they are predefined items.
2045 bool do_translate = it->second.readOnly;
2046 wxString text = do_translate ? wxGetTranslation( it->first ) : it->first;
2047
2048 m_cbBomFmtPresets->SetStringSelection( text );
2049 }
2050 else
2051 {
2052 m_cbBomFmtPresets->SetSelection( presetDashDashDashIndex( m_cbBomFmtPresets->GetCount() ) );
2053 }
2054
2055 int idx = m_cbBomFmtPresets->GetSelection();
2056 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
2057}
2058
2059
2061{
2062 // look at m_userBomFmtPresets to know if aName is a read only preset, or a user preset.
2063 // Read only presets have translated names in UI, so we have to use a translated name in UI selection.
2064 // But for a user preset name we should search for aName (not translated)
2065 wxString ui_label = aName;
2066
2067 for( const auto& [presetName, preset] : m_bomFmtPresets )
2068 {
2069 if( presetName == aName )
2070 {
2071 if( preset.readOnly )
2072 ui_label = wxGetTranslation( aName );
2073
2074 break;
2075 }
2076 }
2077
2078 int idx = m_cbBomFmtPresets->FindString( ui_label );
2079
2080 if( idx >= 0 && m_cbBomFmtPresets->GetSelection() != idx )
2081 {
2082 m_cbBomFmtPresets->SetSelection( idx );
2083 m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
2084 }
2085 else if( idx < 0 )
2086 {
2087 m_cbBomFmtPresets->SetSelection( presetDashDashDashIndex( m_cbBomFmtPresets->GetCount() ) );
2088 }
2089}
2090
2091
2093{
2094 int count = m_cbBomFmtPresets->GetCount();
2095 int index = m_cbBomFmtPresets->GetSelection();
2096
2097 auto resetSelection =
2098 [&]()
2099 {
2101 m_cbBomFmtPresets->SetStringSelection( m_currentBomFmtPreset->name );
2102 else
2103 m_cbBomFmtPresets->SetSelection( presetDashDashDashIndex( m_cbBomFmtPresets->GetCount() ) );
2104 };
2105
2106 if( index == presetDashDashDashIndex( count ) )
2107 {
2108 // Separator: reject the selection
2109 resetSelection();
2110 return;
2111 }
2112 else if( index == presetSavePresetIndex( count ) )
2113 {
2114 // Save current state to new preset
2115 wxString name;
2116
2119
2120 wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
2121
2122 if( dlg.ShowModal() != wxID_OK )
2123 {
2124 resetSelection();
2125 return;
2126 }
2127
2128 name = dlg.GetValue();
2129 bool exists = m_bomFmtPresets.count( name );
2130
2131 if( !exists )
2132 {
2134 m_bomFmtPresets[name].readOnly = false;
2135 m_bomFmtPresets[name].name = name;
2136 }
2137
2139
2140 if( !exists )
2141 {
2142 index = m_cbBomFmtPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
2143 }
2144 else if( preset->readOnly )
2145 {
2146 wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
2147 _( "Error" ), wxOK | wxICON_ERROR, this );
2148 resetSelection();
2149 return;
2150 }
2151 else
2152 {
2153 // Ask the user if they want to overwrite the existing preset
2154 if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
2155 {
2156 resetSelection();
2157 return;
2158 }
2159
2160 *preset = GetCurrentBomFmtSettings();
2161 preset->name = name;
2162
2163 index = m_cbBomFmtPresets->FindString( name );
2164
2165 if( m_bomFmtPresetMRU.Index( name ) != wxNOT_FOUND )
2166 m_bomFmtPresetMRU.Remove( name );
2167 }
2168
2169 m_currentBomFmtPreset = preset;
2170 m_cbBomFmtPresets->SetSelection( index );
2171 m_bomFmtPresetMRU.Insert( name, 0 );
2172
2173 return;
2174 }
2175 else if( index == presetDeletePresetIndex( count ) )
2176 {
2177 // Delete a preset
2178 wxArrayString headers;
2179 std::vector<wxArrayString> items;
2180
2181 headers.Add( _( "Presets" ) );
2182
2183 for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
2184 {
2185 if( !pair.second.readOnly )
2186 {
2187 wxArrayString item;
2188 item.Add( pair.first );
2189 items.emplace_back( item );
2190 }
2191 }
2192
2193 EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
2194 dlg.SetListLabel( _( "Select preset:" ) );
2195
2196 if( dlg.ShowModal() == wxID_OK )
2197 {
2198 wxString presetName = dlg.GetTextSelection();
2199 int idx = m_cbBomFmtPresets->FindString( presetName );
2200
2201 if( idx != wxNOT_FOUND )
2202 {
2203 m_bomFmtPresets.erase( presetName );
2204
2205 m_cbBomFmtPresets->Delete( idx );
2206 m_currentBomFmtPreset = nullptr;
2207 }
2208
2209 if( m_bomFmtPresetMRU.Index( presetName ) != wxNOT_FOUND )
2210 m_bomFmtPresetMRU.Remove( presetName );
2211 }
2212
2213 resetSelection();
2214 return;
2215 }
2216
2217 auto* preset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( index ) );
2218 m_currentBomFmtPreset = preset;
2219
2220 m_lastSelectedBomFmtPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
2221
2222 if( preset )
2223 {
2224 doApplyBomFmtPreset( *preset );
2226 m_currentBomFmtPreset = preset;
2227
2228 if( !m_currentBomFmtPreset->name.IsEmpty() )
2229 {
2230 if( m_bomFmtPresetMRU.Index( preset->name ) != wxNOT_FOUND )
2231 m_bomFmtPresetMRU.Remove( preset->name );
2232
2233 m_bomFmtPresetMRU.Insert( preset->name, 0 );
2234 }
2235 }
2236}
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 OnTableCellClick(wxGridEvent &event) override
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 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...
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 SetCurrentVariant(const wxString &aVariantName)
Set the current variant name for highlighting purposes.
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)
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...
A general-purpose text renderer for WX_GRIDs backed by WX_GRID_TABLE_BASE tables that can handle draw...
Add mouse and command handling (such as cut, copy, and paste) to a WX_GRID instance.
Definition grid_tricks.h: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
void SetColAttr(wxGridCellAttr *aAttr, int aCol) override
Definition wx_grid.h:66
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:92
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:521
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.