KiCad PCB EDA Suite
Loading...
Searching...
No Matches
fields_data_model.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) 2023 <author>
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 along
18 * with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20#include <wx/string.h>
21#include <wx/debug.h>
22#include <wx/grid.h>
23#include <wx/settings.h>
24#include <common.h>
25#include <widgets/wx_grid.h>
26#include <sch_reference_list.h>
27#include <sch_commit.h>
28#include <sch_screen.h>
29#include "string_utils.h"
30
31#include "fields_data_model.h"
32
33
39class GRID_CELL_RESOLVED_TEXT_RENDERER : public wxGridCellStringRenderer
40{
41public:
43 wxGridCellStringRenderer()
44 {
45 }
46
47 void Draw( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC& aDC, const wxRect& aRect, int aRow, int aCol,
48 bool isSelected ) override
49 {
50 wxString value = aGrid.GetCellValue( aRow, aCol );
51
52 if( auto* model = dynamic_cast<FIELDS_EDITOR_GRID_DATA_MODEL*>( aGrid.GetTable() ) )
53 value = model->GetResolvedValue( aRow, aCol );
54
55 wxRect rect = aRect;
56 rect.Inflate( -1 );
57
58 wxGridCellRenderer::Draw( aGrid, aAttr, aDC, aRect, aRow, aCol, isSelected );
59 SetTextColoursAndFont( aGrid, aAttr, aDC, isSelected );
60 aGrid.DrawTextRectangle( aDC, value, rect, wxALIGN_LEFT, wxALIGN_CENTRE );
61 }
62
63 wxSize GetBestSize( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC& aDC, int aRow, int aCol ) override
64 {
65 wxString value = aGrid.GetCellValue( aRow, aCol );
66
67 if( auto* model = dynamic_cast<FIELDS_EDITOR_GRID_DATA_MODEL*>( aGrid.GetTable() ) )
68 value = model->GetResolvedValue( aRow, aCol );
69
70 return wxGridCellStringRenderer::DoGetBestSize( aAttr, aDC, value );
71 }
72
73 wxGridCellRenderer* Clone() const override { return new GRID_CELL_RESOLVED_TEXT_RENDERER(); }
74};
75
76
85static KIID_PATH makeDataStoreKey( const SCH_SHEET_PATH& aSheetPath, const SCH_SYMBOL& aSymbol )
86{
87 KIID_PATH path = aSheetPath.Path();
88 path.push_back( aSymbol.m_Uuid );
89 return path;
90}
91
92
94{
95 switch( aCol )
96 {
97 case DISPLAY_NAME_COLUMN: return _( "Field" );
98 case LABEL_COLUMN: return m_forBOM ? _( "BOM Name" ) : _( "Label" );
99 case SHOW_FIELD_COLUMN: return _( "Include" );
100 case GROUP_BY_COLUMN: return _( "Group By" );
101 default: return wxT( "unknown column" );
102 };
103}
104
105
106wxString VIEW_CONTROLS_GRID_DATA_MODEL::GetValue( int aRow, int aCol )
107{
108 wxCHECK( aRow < GetNumberRows(), wxT( "bad row!" ) );
109
110 BOM_FIELD& rowData = m_fields[aRow];
111
112 switch( aCol )
113 {
115 for( FIELD_T fieldId : MANDATORY_FIELDS )
116 {
117 if( GetDefaultFieldName( fieldId, !DO_TRANSLATE ) == rowData.name )
118 return GetDefaultFieldName( fieldId, DO_TRANSLATE );
119 }
120
121 return rowData.name;
122
123 case LABEL_COLUMN:
124 return rowData.label;
125
126 default:
127 // we can't assert here because wxWidgets sometimes calls this without checking
128 // the column type when trying to see if there's an overflow
129 return wxT( "bad wxWidgets!" );
130 }
131}
132
133
135{
136 wxCHECK( aRow < GetNumberRows(), false );
137
138 BOM_FIELD& rowData = m_fields[aRow];
139
140 switch( aCol )
141 {
142 case SHOW_FIELD_COLUMN: return rowData.show;
143 case GROUP_BY_COLUMN: return rowData.groupBy;
144
145 default:
146 wxFAIL_MSG( wxString::Format( wxT( "column %d doesn't hold a bool value" ), aCol ) );
147 return false;
148 }
149}
150
151
152void VIEW_CONTROLS_GRID_DATA_MODEL::SetValue( int aRow, int aCol, const wxString &aValue )
153{
154 wxCHECK( aRow < GetNumberRows(), /*void*/ );
155
156 BOM_FIELD& rowData = m_fields[aRow];
157
158 switch( aCol )
159 {
161 // Not editable
162 break;
163
164 case LABEL_COLUMN:
165 rowData.label = aValue;
166 break;
167
168 default:
169 wxFAIL_MSG( wxString::Format( wxT( "column %d doesn't hold a string value" ), aCol ) );
170 }
171
172 GetView()->Refresh();
173}
174
175
176void VIEW_CONTROLS_GRID_DATA_MODEL::SetValueAsBool( int aRow, int aCol, bool aValue )
177{
178 wxCHECK( aRow < GetNumberRows(), /*void*/ );
179
180 BOM_FIELD& rowData = m_fields[aRow];
181
182 switch( aCol )
183 {
184 case SHOW_FIELD_COLUMN: rowData.show = aValue; break;
185 case GROUP_BY_COLUMN: rowData.groupBy = aValue; break;
186
187 default:
188 wxFAIL_MSG( wxString::Format( wxT( "column %d doesn't hold a bool value" ), aCol ) );
189 }
190}
191
192
193void VIEW_CONTROLS_GRID_DATA_MODEL::AppendRow( const wxString& aFieldName, const wxString& aBOMName,
194 bool aShow, bool aGroupBy )
195{
196 m_fields.emplace_back( BOM_FIELD{ aFieldName, aBOMName, aShow, aGroupBy } );
197
198 if( wxGrid* grid = GetView() )
199 {
200 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, 1 );
201 grid->ProcessTableMessage( msg );
202 }
203}
204
205
207{
208 wxCHECK( aRow >= 0 && aRow < GetNumberRows(), /* void */ );
209
210 m_fields.erase( m_fields.begin() + aRow );
211
212 if( wxGrid* grid = GetView() )
213 {
214 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow, 1 );
215 grid->ProcessTableMessage( msg );
216 }
217}
218
219
221{
222 wxCHECK( aRow >= 0 && aRow < GetNumberRows(), wxEmptyString );
223
224 BOM_FIELD& rowData = m_fields[aRow];
225
226 return rowData.name;
227}
228
229
230void VIEW_CONTROLS_GRID_DATA_MODEL::SetCanonicalFieldName( int aRow, const wxString& aName )
231{
232 wxCHECK( aRow >= 0 && aRow < GetNumberRows(), /* void */ );
233
234 BOM_FIELD& rowData = m_fields[aRow];
235
236 rowData.name = aName;
237}
238
239
240const wxString FIELDS_EDITOR_GRID_DATA_MODEL::QUANTITY_VARIABLE = wxS( "${QUANTITY}" );
241const wxString FIELDS_EDITOR_GRID_DATA_MODEL::ITEM_NUMBER_VARIABLE = wxS( "${ITEM_NUMBER}" );
242
243
244void FIELDS_EDITOR_GRID_DATA_MODEL::AddColumn( const wxString& aFieldName, const wxString& aLabel,
245 bool aAddedByUser, const wxString& aVariantName )
246{
247 // Don't add a field twice
248 if( GetFieldNameCol( aFieldName ) != -1 )
249 return;
250
251 m_cols.push_back( { aFieldName, aLabel, aAddedByUser, false, false } );
252
253 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
254 updateDataStoreSymbolField( m_symbolsList[i], aFieldName, aVariantName );
255}
256
257
259 const wxString& aFieldName,
260 const wxString& aVariantName )
261{
262 const SCH_SYMBOL* symbol = aSymbolRef.GetSymbol();
263
264 if( !symbol )
265 return;
266
267 KIID_PATH key = makeDataStoreKey( aSymbolRef.GetSheetPath(), *symbol );
268
269 if( isAttribute( aFieldName ) )
270 {
271 m_dataStore[key][aFieldName] = getAttributeValue( aSymbolRef, aFieldName, aVariantName );
272 }
273 else if( const SCH_FIELD* field = symbol->FindFieldCaseInsensitive( aFieldName ) )
274 {
275 if( field->IsPrivate() )
276 {
277 m_dataStore[key][aFieldName] = wxEmptyString;
278 return;
279 }
280
281 wxString value = symbol->Schematic()->ConvertKIIDsToRefs( field->GetText( &aSymbolRef.GetSheetPath(),
282 aVariantName ) );
283
284 m_dataStore[key][aFieldName] = value;
285 }
286 else if( IsGeneratedField( aFieldName ) )
287 {
288 // Handle generated fields with variables as names (e.g. ${QUANTITY}) that are not present in
289 // the symbol by giving them the correct value
290 m_dataStore[key][aFieldName] = aFieldName;
291 }
292 else
293 {
294 m_dataStore[key][aFieldName] = wxEmptyString;
295 }
296}
297
298
300{
301 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
302 {
303 if( SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol() )
304 {
305 KIID_PATH key = makeDataStoreKey( m_symbolsList[i].GetSheetPath(), *symbol );
306 m_dataStore[key].erase( m_cols[aCol].m_fieldName );
307 }
308 }
309
310 m_cols.erase( m_cols.begin() + aCol );
311
312 if( wxGrid* grid = GetView() )
313 {
314 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_COLS_DELETED, aCol, 1 );
315 grid->ProcessTableMessage( msg );
316 }
317}
318
319
320void FIELDS_EDITOR_GRID_DATA_MODEL::RenameColumn( int aCol, const wxString& newName )
321{
322 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
323 {
324 SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol();
325 KIID_PATH key = makeDataStoreKey( m_symbolsList[i].GetSheetPath(), *symbol );
326
327 // Careful; field may have already been renamed from another sheet instance
328 if( auto node = m_dataStore[key].extract( m_cols[aCol].m_fieldName ) )
329 {
330 node.key() = newName;
331 m_dataStore[key].insert( std::move( node ) );
332 }
333 }
334
335 m_cols[aCol].m_fieldName = newName;
336 m_cols[aCol].m_label = newName;
337}
338
339
340int FIELDS_EDITOR_GRID_DATA_MODEL::GetFieldNameCol( const wxString& aFieldName ) const
341{
342 for( size_t i = 0; i < m_cols.size(); i++ )
343 {
344 if( m_cols[i].m_fieldName.CmpNoCase( aFieldName ) == 0 )
345 return static_cast<int>( i );
346 }
347
348 return -1;
349}
350
351
353{
354 std::vector<BOM_FIELD> fields;
355
356 for( const DATA_MODEL_COL& col : m_cols )
357 fields.push_back( { col.m_fieldName, col.m_label, col.m_show, col.m_group } );
358
359 return fields;
360}
361
362
363void FIELDS_EDITOR_GRID_DATA_MODEL::SetFieldsOrder( const std::vector<wxString>& aNewOrder )
364{
365 size_t foundCount = 0;
366
367 for( const wxString& newField : aNewOrder )
368 {
369 if( foundCount >= m_cols.size() )
370 break;
371
372 for( DATA_MODEL_COL& col : m_cols )
373 {
374 if( col.m_fieldName == newField )
375 {
376 std::swap( m_cols[foundCount], col );
377 foundCount++;
378 break;
379 }
380 }
381 }
382}
383
384
386{
387 // Check if aCol is the first visible column
388 for( int col = 0; col < aCol; ++col )
389 {
390 if( m_cols[col].m_show )
391 return false;
392 }
393
394 return true;
395}
396
397
398wxString FIELDS_EDITOR_GRID_DATA_MODEL::GetValue( int aRow, int aCol )
399{
400 GetView()->SetReadOnly( aRow, aCol, IsExpanderColumn( aCol ) );
401 return GetValue( m_rows[aRow], aCol );
402}
403
404
406{
407 return GetValue( m_rows[aRow], aCol, wxT( ", " ), wxT( "-" ), true, false );
408}
409
410
411wxGridCellAttr* FIELDS_EDITOR_GRID_DATA_MODEL::GetAttr( int aRow, int aCol, wxGridCellAttr::wxAttrKind aKind )
412{
413 wxGridCellAttr* attr = nullptr;
414 bool needsUrlEditor = false;
415 bool needsVariantHighlight = false;
416 bool needsTextVarRenderer = false;
417 wxColour highlightColor;
418
419 // Check if we need URL editor
421 || IsURL( GetValue( m_rows[aRow], aCol ) ) )
422 {
423 if( m_urlEditor )
424 needsUrlEditor = true;
425 }
426
427 // Check if the raw value contains a text variable that should be resolved for display
428 if( aRow >= 0 && aRow < (int) m_rows.size() && aCol >= 0 && aCol < (int) m_cols.size() && !ColIsReference( aCol )
429 && !ColIsQuantity( aCol ) && !ColIsItemNumber( aCol ) )
430 {
431 wxString rawValue = GetValue( m_rows[aRow], aCol );
432
433 if( rawValue.Contains( wxT( "${" ) ) )
434 needsTextVarRenderer = true;
435 }
436
437 // Check if we need variant highlighting
438 if( !m_currentVariant.IsEmpty() && aRow >= 0 && aRow < (int) m_rows.size()
439 && aCol >= 0 && aCol < (int) m_cols.size() )
440 {
441 const wxString& fieldName = m_cols[aCol].m_fieldName;
442
443 // Skip Reference and generated fields (like ${QUANTITY}) for highlighting
444 if( !ColIsReference( aCol ) && !ColIsQuantity( aCol ) && !ColIsItemNumber( aCol ) )
445 {
446 const DATA_MODEL_ROW& row = m_rows[aRow];
447
448 // Check if any symbol in this row has a variant-specific value
449 for( const SCH_REFERENCE& ref : row.m_Refs )
450 {
451 wxString defaultValue = getDefaultFieldValue( ref, fieldName );
452
453 KIID_PATH symbolKey = KIID_PATH();
454
455 if( const SCH_SYMBOL* symbol = ref.GetSymbol() )
456 {
457 symbolKey = ref.GetSheetPath().Path();
458 symbolKey.push_back( symbol->m_Uuid );
459 }
460
461 // Get the current value from the data store
462 wxString currentValue;
463
464 if( m_dataStore.contains( symbolKey ) && m_dataStore[symbolKey].contains( fieldName ) )
465 currentValue = m_dataStore[symbolKey][fieldName];
466
467 if( currentValue != defaultValue )
468 {
469 needsVariantHighlight = true;
470
471 // Use a subtle highlight color that works in both light and dark themes
472 wxColour bg = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW );
473 bool isDark = ( bg.Red() + bg.Green() + bg.Blue() ) < 384;
474
475 if( isDark )
476 highlightColor = wxColour( 80, 80, 40 ); // Dark gold/brown
477 else
478 highlightColor = wxColour( 255, 255, 200 ); // Light yellow
479
480 break;
481 }
482 }
483 }
484 }
485
486 // If we don't need any custom attributes, use the base class behavior
487 if( !needsUrlEditor && !needsVariantHighlight && !needsTextVarRenderer )
488 return WX_GRID_TABLE_BASE::GetAttr( aRow, aCol, aKind );
489
490 // URL cells: use m_urlEditor as base, potentially with variant highlight overlay
491 if( needsUrlEditor )
492 {
493 if( needsVariantHighlight )
494 {
495 // Clone the URL editor attribute and add highlight color
496 attr = m_urlEditor->Clone();
497 attr->SetBackgroundColour( highlightColor );
498 }
499 else
500 {
501 // Just use the URL editor attribute directly
502 m_urlEditor->IncRef();
503 attr = m_urlEditor;
504 }
505
506 return enhanceAttr( attr, aRow, aCol, aKind );
507 }
508
509 // Non-URL cells: start with column attributes if they exist.
510 // This preserves checkbox renderers and other column-specific settings.
511 if( m_colAttrs.find( aCol ) != m_colAttrs.end() && m_colAttrs[aCol] )
512 {
513 attr = m_colAttrs[aCol]->Clone();
514 }
515 else
516 {
517 attr = new wxGridCellAttr();
518 }
519
520 if( needsVariantHighlight )
521 attr->SetBackgroundColour( highlightColor );
522
523 if( needsTextVarRenderer )
524 {
525 if( !m_textVarRenderer )
527
528 m_textVarRenderer->IncRef();
529 attr->SetRenderer( m_textVarRenderer );
530
531 // Tint text-var cells if not already highlighted by variant
532 if( !needsVariantHighlight )
533 {
534 wxColour bg = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW );
535 bool isDark = ( bg.Red() + bg.Green() + bg.Blue() ) < 384;
536
537 attr->SetBackgroundColour( isDark ? wxColour( 80, 70, 30 ) // Dark amber
538 : wxColour( 255, 252, 200 ) ); // Light yellow
539 }
540 }
541
542 return enhanceAttr( attr, aRow, aCol, aKind );
543}
544
545
547 const wxString& refDelimiter,
548 const wxString& refRangeDelimiter,
549 bool resolveVars,
550 bool listMixedValues )
551{
552 std::vector<SCH_REFERENCE> references;
553 std::set<wxString> mixedValues;
554 wxString fieldValue;
555
556 for( const SCH_REFERENCE& ref : group.m_Refs )
557 {
558 if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
559 {
560 references.push_back( ref );
561 }
562 else // Other columns are either a single value or ROW_MULTI_ITEMS
563 {
564 KIID_PATH symbolKey = makeDataStoreKey( ref.GetSheetPath(), *ref.GetSymbol() );
565
566 if( !m_dataStore.contains( symbolKey ) || !m_dataStore[symbolKey].contains( m_cols[aCol].m_fieldName ) )
567 return INDETERMINATE_STATE;
568
569 wxString refFieldValue = m_dataStore[symbolKey][m_cols[aCol].m_fieldName];
570
571 if( resolveVars )
572 {
573 if( IsGeneratedField( m_cols[aCol].m_fieldName ) )
574 {
575 // Generated fields (e.g. ${QUANTITY}) can't have un-applied values as they're
576 // read-only. Resolve them against the field.
577 refFieldValue = getFieldShownText( ref, m_cols[aCol].m_fieldName );
578 }
579 else if( refFieldValue.Contains( wxT( "${" ) ) )
580 {
581 // Resolve variables in the un-applied value using the parent symbol and instance
582 // data.
583 std::function<bool( wxString* )> symbolResolver =
584 [&]( wxString* token ) -> bool
585 {
586 return ref.GetSymbol()->ResolveTextVar( &ref.GetSheetPath(), token );
587 };
588
589 refFieldValue = ExpandTextVars( refFieldValue, & symbolResolver );
590 }
591 }
592
593 if( listMixedValues )
594 mixedValues.insert( refFieldValue );
595 else if( &ref == &group.m_Refs.front() )
596 fieldValue = refFieldValue;
597 else if( fieldValue != refFieldValue )
598 return INDETERMINATE_STATE;
599 }
600 }
601
602 if( listMixedValues )
603 {
604 fieldValue = wxEmptyString;
605
606 for( const wxString& value : mixedValues )
607 {
608 if( value.IsEmpty() )
609 continue;
610 else if( fieldValue.IsEmpty() )
611 fieldValue = value;
612 else
613 fieldValue += "," + value;
614 }
615 }
616
617 if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
618 {
619 // Remove duplicates (other units of multi-unit parts)
620 std::sort( references.begin(), references.end(),
621 []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
622 {
623 wxString l_ref( l.GetRef() << l.GetRefNumber() );
624 wxString r_ref( r.GetRef() << r.GetRefNumber() );
625 return StrNumCmp( l_ref, r_ref, true ) < 0;
626 } );
627
628 auto logicalEnd = std::unique( references.begin(), references.end(),
629 []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
630 {
631 // If unannotated then we can't tell what units belong together
632 // so we have to leave them all
633 if( l.GetRefNumber() == wxT( "?" ) )
634 return false;
635
636 wxString l_ref( l.GetRef() << l.GetRefNumber() );
637 wxString r_ref( r.GetRef() << r.GetRefNumber() );
638 return l_ref == r_ref;
639 } );
640
641 references.erase( logicalEnd, references.end() );
642 }
643
644 if( ColIsReference( aCol ) )
645 fieldValue = SCH_REFERENCE_LIST::Shorthand( references, refDelimiter, refRangeDelimiter );
646 else if( ColIsQuantity( aCol ) )
647 fieldValue = wxString::Format( wxT( "%d" ), (int) references.size() );
648 else if( ColIsItemNumber( aCol ) && group.m_Flag != CHILD_ITEM )
649 fieldValue = wxString::Format( wxT( "%d" ), group.m_ItemNumber );
650
651 return fieldValue;
652}
653
654
655void FIELDS_EDITOR_GRID_DATA_MODEL::SetValue( int aRow, int aCol, const wxString& aValue )
656{
657 wxCHECK_RET( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), wxS( "Invalid column number" ) );
658
659 // Can't modify references or generated fields (e.g. ${QUANTITY})
660 if( ColIsReference( aCol )
661 || ( IsGeneratedField( m_cols[aCol].m_fieldName ) && !ColIsAttribute( aCol ) ) )
662 {
663 return;
664 }
665
666 DATA_MODEL_ROW& rowGroup = m_rows[aRow];
667
668 const SCH_SYMBOL* sharedSymbol = nullptr;
669 bool isSharedInstance = false;
670
671 for( const SCH_REFERENCE& ref : rowGroup.m_Refs )
672 {
673 const SCH_SCREEN* screen = nullptr;
674
675 // Check to see if the symbol associated with this row has more than one instance.
676 if( const SCH_SYMBOL* symbol = ref.GetSymbol() )
677 {
678 screen = static_cast<const SCH_SCREEN*>( symbol->GetParent() );
679
680 isSharedInstance = ( screen && ( screen->GetRefCount() > 1 ) );
681 sharedSymbol = symbol;
682 }
683
684 KIID_PATH key = makeDataStoreKey( ref.GetSheetPath(), *ref.GetSymbol() );
685 m_dataStore[key][m_cols[aCol].m_fieldName] = aValue;
686 }
687
688 // Update all of the other instances for the shared symbol as required.
689 if( isSharedInstance
690 && ( ( rowGroup.m_Flag == GROUP_SINGLETON ) || ( rowGroup.m_Flag == CHILD_ITEM ) ) )
691 {
692 for( DATA_MODEL_ROW& row : m_rows )
693 {
694 if( row.m_ItemNumber == aRow + 1 )
695 continue;
696
697 for( const SCH_REFERENCE& ref : row.m_Refs )
698 {
699 if( ref.GetSymbol() != sharedSymbol )
700 continue;
701
702 KIID_PATH key = makeDataStoreKey( ref.GetSheetPath(), *ref.GetSymbol() );
703 m_dataStore[key][m_cols[aCol].m_fieldName] = aValue;
704 }
705 }
706 }
707
708 m_edited = true;
709}
710
711
713{
714 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
715 return m_cols[aCol].m_fieldName == GetCanonicalFieldName( FIELD_T::REFERENCE );
716}
717
718
720{
721 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
722 return m_cols[aCol].m_fieldName == GetCanonicalFieldName( FIELD_T::VALUE );
723}
724
725
727{
728 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
729 return m_cols[aCol].m_fieldName == QUANTITY_VARIABLE;
730}
731
732
734{
735 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
736 return m_cols[aCol].m_fieldName == ITEM_NUMBER_VARIABLE;
737}
738
739
741{
742 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
743 return isAttribute( m_cols[aCol].m_fieldName );
744}
745
746
748 const DATA_MODEL_ROW& rhGroup,
749 FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol,
750 bool ascending )
751{
752 // Empty rows always go to the bottom, whether ascending or descending
753 if( lhGroup.m_Refs.size() == 0 )
754 return true;
755 else if( rhGroup.m_Refs.size() == 0 )
756 return false;
757
758 // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
759 // to get the opposite sort. i.e. ~(a<b) != (a>b)
760 auto local_cmp =
761 [ ascending ]( const auto a, const auto b )
762 {
763 if( ascending )
764 return a < b;
765 else
766 return a > b;
767 };
768
769 // Primary sort key is sortCol; secondary is always REFERENCE (column 0)
770 if( sortCol < 0 || sortCol >= dataModel->GetNumberCols() )
771 sortCol = 0;
772
773 wxString lhs = dataModel->GetValue( lhGroup, sortCol, wxT( ", " ), wxT( "-" ), true ).Trim( true ).Trim( false );
774 wxString rhs = dataModel->GetValue( rhGroup, sortCol, wxT( ", " ), wxT( "-" ), true ).Trim( true ).Trim( false );
775
776 if( lhs == rhs || dataModel->ColIsReference( sortCol ) )
777 {
778 wxString lhRef = lhGroup.m_Refs[0].GetRef() + lhGroup.m_Refs[0].GetRefNumber();
779 wxString rhRef = rhGroup.m_Refs[0].GetRef() + rhGroup.m_Refs[0].GetRefNumber();
780 return local_cmp( StrNumCmp( lhRef, rhRef, true ), 0 );
781 }
782 else
783 {
784 return local_cmp( ValueStringCompare( lhs, rhs ), 0 );
785 }
786}
787
788
790{
792
793 // We're going to sort the rows based on their first reference, so the first reference
794 // had better be the lowest one.
795 for( DATA_MODEL_ROW& row : m_rows )
796 {
797 std::sort( row.m_Refs.begin(), row.m_Refs.end(),
798 []( const SCH_REFERENCE& lhs, const SCH_REFERENCE& rhs )
799 {
800 wxString lhs_ref( lhs.GetRef() << lhs.GetRefNumber() );
801 wxString rhs_ref( rhs.GetRef() << rhs.GetRefNumber() );
802 return StrNumCmp( lhs_ref, rhs_ref, true ) < 0;
803 } );
804 }
805
806 std::sort( m_rows.begin(), m_rows.end(),
807 [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
808 {
809 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
810 } );
811
812 // Time to renumber the item numbers
813 int itemNumber = 1;
814
815 for( DATA_MODEL_ROW& row : m_rows )
816 {
817 row.m_ItemNumber = itemNumber++;
818 }
819
821}
822
823
825{
826 // If items are unannotated then we can't tell if they're units of the same symbol or not
827 if( lhRef.GetRefNumber() == wxT( "?" ) )
828 return false;
829
830 return ( lhRef.GetRef() == rhRef.GetRef() && lhRef.GetRefNumber() == rhRef.GetRefNumber() );
831}
832
833
835{
837 bool matchFound = false;
838
839 if( refCol == -1 )
840 return false;
841
842 // First check the reference column. This can be done directly out of the
843 // SCH_REFERENCEs as the references can't be edited in the grid.
844 if( m_cols[refCol].m_group )
845 {
846 // if we're grouping by reference, then only the prefix must match
847 if( lhRef.GetRef() != rhRef.GetRef() )
848 return false;
849
850 matchFound = true;
851 }
852
853 KIID_PATH lhRefKey = makeDataStoreKey( lhRef.GetSheetPath(), *lhRef.GetSymbol() );
854 KIID_PATH rhRefKey = makeDataStoreKey( rhRef.GetSheetPath(), *rhRef.GetSymbol() );
855
856 // Now check all the other columns.
857 for( size_t i = 0; i < m_cols.size(); ++i )
858 {
859 //Handled already
860 if( static_cast<int>( i ) == refCol )
861 continue;
862
863 if( !m_cols[i].m_group )
864 continue;
865
866 // If the field is generated (e.g. ${QUANTITY}), we need to resolve it through the symbol
867 // to get the actual current value; otherwise we need to pull it out of the store so the
868 // refresh can regroup based on values that haven't been applied to the schematic yet.
869 wxString lh, rh;
870
871 if( IsGeneratedField( m_cols[i].m_fieldName )
872 || IsGeneratedField( m_dataStore[lhRefKey][m_cols[i].m_fieldName] ) )
873 {
874 lh = getFieldShownText( lhRef, m_cols[i].m_fieldName );
875 }
876 else
877 {
878 lh = m_dataStore[lhRefKey][m_cols[i].m_fieldName];
879 }
880
881 if( IsGeneratedField( m_cols[i].m_fieldName )
882 || IsGeneratedField( m_dataStore[rhRefKey][m_cols[i].m_fieldName] ) )
883 {
884 rh = getFieldShownText( rhRef, m_cols[i].m_fieldName );
885 }
886 else
887 {
888 rh = m_dataStore[rhRefKey][m_cols[i].m_fieldName];
889 }
890
891 if( lh != rh )
892 return false;
893
894 matchFound = true;
895 }
896
897 return matchFound;
898}
899
900
902 const wxString& aFieldName )
903{
904 SCH_FIELD* field = aRef.GetSymbol()->GetField( aFieldName );
905
906 if( field )
907 {
908 if( field->IsPrivate() )
909 return wxEmptyString;
910 else
911 return field->GetShownText( &aRef.GetSheetPath(), false );
912 }
913
914 // Handle generated fields with variables as names (e.g. ${QUANTITY}) that are not present in
915 // the symbol by giving them the correct value by resolving against the symbol
916 if( IsGeneratedField( aFieldName ) )
917 {
918 int depth = 0;
919 const SCH_SHEET_PATH& path = aRef.GetSheetPath();
920
921 std::function<bool( wxString* )> symbolResolver =
922 [&]( wxString* token ) -> bool
923 {
924 return aRef.GetSymbol()->ResolveTextVar( &path, token, depth + 1 );
925 };
926
927 return ExpandTextVars( aFieldName, &symbolResolver );
928 }
929
930 return wxEmptyString;
931}
932
933
934bool FIELDS_EDITOR_GRID_DATA_MODEL::isAttribute( const wxString& aFieldName )
935{
936 return aFieldName == wxS( "${DNP}" )
937 || aFieldName == wxS( "${EXCLUDE_FROM_BOARD}" )
938 || aFieldName == wxS( "${EXCLUDE_FROM_BOM}" )
939 || aFieldName == wxS( "${EXCLUDE_FROM_SIM}" );
940}
941
942
943wxString FIELDS_EDITOR_GRID_DATA_MODEL::getAttributeValue( const SCH_REFERENCE& aRef, const wxString& aAttributeName,
944 const wxString& aVariantName )
945{
946 if( aAttributeName == wxS( "${DNP}" ) )
947 return aRef.GetSymbolDNP( aVariantName ) ? wxS( "1" ) : wxS( "0" );
948
949 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
950 return aRef.GetSymbolExcludedFromBoard() ? wxS( "1" ) : wxS( "0" );
951
952 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
953 return aRef.GetSymbolExcludedFromBOM( aVariantName ) ? wxS( "1" ) : wxS( "0" );
954
955 if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
956 return aRef.GetSymbolExcludedFromSim( aVariantName ) ? wxS( "1" ) : wxS( "0" );
957
958 return wxS( "0" );
959}
960
961
963 const wxString& aFieldName )
964{
965 const SCH_SYMBOL* symbol = aRef.GetSymbol();
966
967 if( !symbol )
968 return wxEmptyString;
969
970 // For attributes, get the default (non-variant) value
971 if( isAttribute( aFieldName ) )
972 return getAttributeValue( aRef, aFieldName, wxEmptyString );
973
974 // For regular fields, get the text without variant override
975 if( const SCH_FIELD* field = symbol->GetField( aFieldName ) )
976 {
977 if( field->IsPrivate() )
978 return wxEmptyString;
979
980 // Get the field text with empty variant name (default value)
981 wxString value = symbol->Schematic()->ConvertKIIDsToRefs(
982 field->GetText( &aRef.GetSheetPath(), wxEmptyString ) );
983 return value;
984 }
985
986 // For generated fields, return the field name itself
987 if( IsGeneratedField( aFieldName ) )
988 return aFieldName;
989
990 return wxEmptyString;
991}
992
993
995 const wxString& aAttributeName,
996 const wxString& aValue,
997 const wxString& aVariantName )
998{
999 bool attrChanged = false;
1000 bool newValue = aValue == wxS( "1" );
1001
1002 if( aAttributeName == wxS( "${DNP}" ) )
1003 {
1004 attrChanged = aRef.GetSymbolDNP( aVariantName ) != newValue;
1005 aRef.SetSymbolDNP( newValue, aVariantName );
1006 }
1007 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
1008 {
1009 attrChanged = aRef.GetSymbolExcludedFromBoard() != newValue;
1010 aRef.SetSymbolExcludedFromBoard( newValue );
1011 }
1012 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
1013 {
1014 attrChanged = aRef.GetSymbolExcludedFromBOM( aVariantName ) != newValue;
1015 aRef.SetSymbolExcludedFromBOM( newValue, aVariantName );
1016 }
1017 else if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
1018 {
1019 attrChanged = aRef.GetSymbolExcludedFromSim( aVariantName ) != newValue;
1020 aRef.SetSymbolExcludedFromSim( newValue, aVariantName );
1021 }
1022
1023 return attrChanged;
1024}
1025
1026
1031
1032
1037
1038
1040{
1041 if( !m_rebuildsEnabled )
1042 return;
1043
1044 if( GetView() )
1045 {
1046 // Commit any pending in-place edits before the row gets moved out from under
1047 // the editor.
1048 static_cast<WX_GRID*>( GetView() )->CommitPendingChanges( true );
1049
1050 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
1051 GetView()->ProcessTableMessage( msg );
1052 }
1053
1054 m_rows.clear();
1055
1056 EDA_COMBINED_MATCHER matcher( m_filter.Lower(), CTX_SEARCH );
1057
1058 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
1059 {
1061
1062 if( !m_filter.IsEmpty() && !matcher.Find( ref.GetFullRef().Lower() ) )
1063 continue;
1064
1065 if( m_excludeDNP )
1066 {
1067 bool isDNP = false;
1068
1069 if( !m_variantNames.empty() )
1070 {
1071 for( const wxString& variantName : m_variantNames )
1072 {
1073 if( ref.GetSymbol()->ResolveDNP( &ref.GetSheetPath(), variantName )
1074 || ref.GetSheetPath().GetDNP( variantName ) )
1075 {
1076 isDNP = true;
1077 break;
1078 }
1079 }
1080 }
1081 else
1082 {
1083 isDNP = ref.GetSymbol()->ResolveDNP( &ref.GetSheetPath(), m_currentVariant )
1085 }
1086
1087 if( isDNP )
1088 continue;
1089 }
1090
1091 if( !m_includeExcluded )
1092 {
1093 bool isExcluded = false;
1094
1095 if( !m_variantNames.empty() )
1096 {
1097 for( const wxString& variantName : m_variantNames )
1098 {
1099 if( ref.GetSymbol()->ResolveExcludedFromBOM( &ref.GetSheetPath(), variantName )
1100 || ref.GetSheetPath().GetExcludedFromBOM( variantName ) )
1101 {
1102 isExcluded = true;
1103 break;
1104 }
1105 }
1106 }
1107 else
1108 {
1109 isExcluded = ref.GetSymbol()->ResolveExcludedFromBOM( &ref.GetSheetPath(), m_currentVariant )
1111 }
1112
1113 if( isExcluded )
1114 continue;
1115 }
1116
1117 // Check if the symbol if on the current sheet or, in the sheet path somewhere
1118 // depending on scope
1119 if( ( m_scope == SCOPE::SCOPE_SHEET && ref.GetSheetPath() != m_path )
1120 || ( m_scope == SCOPE::SCOPE_SHEET_RECURSIVE
1121 && !ref.GetSheetPath().IsContainedWithin( m_path ) ) )
1122 {
1123 continue;
1124 }
1125
1126 bool matchFound = false;
1127
1128 // Performance optimization for ungrouped case to skip the N^2 for loop
1129 if( !m_groupingEnabled && !ref.IsMultiUnit() )
1130 {
1131 m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
1132 continue;
1133 }
1134
1135 // See if we already have a row which this symbol fits into
1136 for( DATA_MODEL_ROW& row : m_rows )
1137 {
1138 // all group members must have identical refs so just use the first one
1139 SCH_REFERENCE rowRef = row.m_Refs[0];
1140
1141 if( unitMatch( ref, rowRef ) )
1142 {
1143 matchFound = true;
1144 row.m_Refs.push_back( ref );
1145 break;
1146 }
1147 else if( m_groupingEnabled && groupMatch( ref, rowRef ) )
1148 {
1149 matchFound = true;
1150 row.m_Refs.push_back( ref );
1151 row.m_Flag = GROUP_COLLAPSED;
1152 break;
1153 }
1154 }
1155
1156 if( !matchFound )
1157 m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
1158 }
1159
1160 if( GetView() )
1161 {
1162 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() );
1163 GetView()->ProcessTableMessage( msg );
1164 }
1165
1166 Sort();
1167}
1168
1169
1171{
1172 std::vector<DATA_MODEL_ROW> children;
1173
1174 for( SCH_REFERENCE& ref : m_rows[aRow].m_Refs )
1175 {
1176 bool matchFound = false;
1177
1178 // See if we already have a child group which this symbol fits into
1179 for( DATA_MODEL_ROW& child : children )
1180 {
1181 // group members are by definition all matching, so just check
1182 // against the first member
1183 if( unitMatch( ref, child.m_Refs[0] ) )
1184 {
1185 matchFound = true;
1186 child.m_Refs.push_back( ref );
1187 break;
1188 }
1189 }
1190
1191 if( !matchFound )
1192 children.emplace_back( DATA_MODEL_ROW( ref, CHILD_ITEM ) );
1193 }
1194
1195 if( children.size() < 2 )
1196 return;
1197
1198 std::sort( children.begin(), children.end(),
1199 [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
1200 {
1201 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
1202 } );
1203
1204 m_rows[aRow].m_Flag = GROUP_EXPANDED;
1205 m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() );
1206
1207 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, children.size() );
1208 GetView()->ProcessTableMessage( msg );
1209}
1210
1211
1213{
1214 auto firstChild = m_rows.begin() + aRow + 1;
1215 auto afterLastChild = firstChild;
1216 int deleted = 0;
1217
1218 while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM )
1219 {
1220 deleted++;
1221 afterLastChild++;
1222 }
1223
1224 m_rows[aRow].m_Flag = GROUP_COLLAPSED;
1225 m_rows.erase( firstChild, afterLastChild );
1226
1227 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted );
1228 GetView()->ProcessTableMessage( msg );
1229}
1230
1231
1233{
1234 DATA_MODEL_ROW& group = m_rows[aRow];
1235
1236 if( group.m_Flag == GROUP_COLLAPSED )
1237 ExpandRow( aRow );
1238 else if( group.m_Flag == GROUP_EXPANDED )
1239 CollapseRow( aRow );
1240}
1241
1242
1244{
1245 for( size_t i = 0; i < m_rows.size(); ++i )
1246 {
1247 if( m_rows[i].m_Flag == GROUP_EXPANDED )
1248 {
1249 CollapseRow( i );
1251 }
1252 }
1253}
1254
1255
1257{
1258 for( size_t i = 0; i < m_rows.size(); ++i )
1259 {
1260 if( m_rows[i].m_Flag == GROUP_COLLAPSED_DURING_SORT )
1261 ExpandRow( i );
1262 }
1263}
1264
1265
1267 const wxString& aVariantName )
1268{
1269 bool symbolModified = false;
1270 std::unique_ptr<SCH_SYMBOL> symbolCopy;
1271
1272 for( size_t i = 0; i < m_symbolsList.GetCount(); i++ )
1273 {
1274 SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol();
1275 SCH_SYMBOL* nextSymbol = nullptr;
1276
1277 if( ( i + 1 ) < m_symbolsList.GetCount() )
1278 nextSymbol = m_symbolsList[i + 1].GetSymbol();
1279
1280 if( i == 0 )
1281 symbolCopy = std::make_unique<SCH_SYMBOL>( *symbol );
1282
1283 KIID_PATH key = makeDataStoreKey( m_symbolsList[i].GetSheetPath(), *symbol );
1284 const std::map<wxString, wxString>& fieldStore = m_dataStore[key];
1285
1286 for( const auto& [srcName, srcValue] : fieldStore )
1287 {
1288 // Attributes bypass the field logic, so handle them first
1289 if( isAttribute( srcName ) )
1290 {
1291 symbolModified |= setAttributeValue( m_symbolsList[i], srcName, srcValue, aVariantName );
1292 continue;
1293 }
1294
1295 // Skip generated fields with variables as names (e.g. ${QUANTITY});
1296 // they can't be edited
1297 if( IsGeneratedField( srcName ) )
1298 continue;
1299
1300 SCH_FIELD* destField = symbol->FindFieldCaseInsensitive( srcName );
1301
1302 if( destField && !destField->IsMandatory() && destField->GetName() != srcName )
1303 {
1304 destField->SetName( srcName );
1305 symbolModified = true;
1306 }
1307
1308 if( destField && destField->IsPrivate() )
1309 {
1310 if( srcValue.IsEmpty() )
1311 continue;
1312 else
1313 destField->SetPrivate( false );
1314 }
1315
1316 int col = GetFieldNameCol( srcName );
1317 bool userAdded = ( col != -1 && m_cols[col].m_userAdded );
1318
1319 // Add a not existing field if it has a value for this symbol
1320 bool createField = !destField && ( !srcValue.IsEmpty() || userAdded );
1321
1322 if( createField )
1323 {
1324 destField = symbol->AddField( SCH_FIELD( symbol, FIELD_T::USER, srcName ) );
1325 destField->SetTextAngle( symbol->GetField( FIELD_T::REFERENCE )->GetTextAngle() );
1326
1327 if( const TEMPLATE_FIELDNAME* srcTemplate = aTemplateFieldnames.GetFieldName( srcName ) )
1328 destField->SetVisible( srcTemplate->m_Visible );
1329 else
1330 destField->SetVisible( false );
1331
1332 destField->SetTextPos( symbol->GetPosition() );
1333 symbolModified = true;
1334 }
1335
1336 if( !destField )
1337 continue;
1338
1339 // Reference is not editable from this dialog
1340 if( destField->GetId() == FIELD_T::REFERENCE )
1341 continue;
1342
1343 wxString previousValue = destField->GetText( &m_symbolsList[i].GetSheetPath(), aVariantName );
1344
1345 destField->SetText( symbol->Schematic()->ConvertRefsToKIIDs( srcValue ), &m_symbolsList[i].GetSheetPath(),
1346 aVariantName );
1347
1348 if( !createField && ( previousValue != srcValue ) )
1349 symbolModified = true;
1350 }
1351
1352 for( int ii = static_cast<int>( symbol->GetFields().size() ) - 1; ii >= 0; ii-- )
1353 {
1354 if( symbol->GetFields()[ii].IsMandatory() || symbol->GetFields()[ii].IsPrivate() )
1355 continue;
1356
1357 const wxString& existingName = symbol->GetFields()[ii].GetName();
1358
1359 bool stillTracked = std::any_of( fieldStore.begin(), fieldStore.end(),
1360 [&]( const auto& kv )
1361 {
1362 return kv.first.IsSameAs( existingName, false );
1363 } );
1364
1365 if( !stillTracked )
1366 {
1367 symbol->GetFields().erase( symbol->GetFields().begin() + ii );
1368 symbolModified = true;
1369 }
1370 }
1371
1372 if( symbolModified && ( symbol != nextSymbol ) )
1373 aCommit.Modified( symbol, symbolCopy.release(), m_symbolsList[i].GetSheetPath().LastScreen() );
1374
1375 // Only reset the modified flag and next symbol copy if the next symbol is different from the current one.
1376 if( symbol != nextSymbol )
1377 {
1378 if( nextSymbol )
1379 symbolCopy = std::make_unique<SCH_SYMBOL>( *nextSymbol );
1380 else
1381 symbolCopy.reset( nullptr );
1382
1383 symbolModified = false;
1384 }
1385 }
1386
1387 m_edited = false;
1388}
1389
1390
1392{
1393 int width = 0;
1394
1395 if( ColIsReference( aCol ) )
1396 {
1397 for( int row = 0; row < GetNumberRows(); ++row )
1398 width = std::max( width, KIUI::GetTextSize( GetValue( row, aCol ), GetView() ).x );
1399 }
1400 else
1401 {
1402 wxString fieldName = GetColFieldName( aCol ); // symbol fieldName or Qty string
1403
1404 for( unsigned symbolRef = 0; symbolRef < m_symbolsList.GetCount(); ++symbolRef )
1405 {
1406 KIID_PATH key = makeDataStoreKey( m_symbolsList[symbolRef].GetSheetPath(),
1407 *m_symbolsList[symbolRef].GetSymbol() );
1408 wxString text = m_dataStore[key][fieldName];
1409
1410 width = std::max( width, KIUI::GetTextSize( text, GetView() ).x );
1411 }
1412 }
1413
1414 return width;
1415}
1416
1417
1418void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyBomPreset( const BOM_PRESET& aPreset, const wxString& aVariantName )
1419{
1420 // Hide and un-group everything by default
1421 for( size_t i = 0; i < m_cols.size(); i++ )
1422 {
1423 SetShowColumn( i, false );
1424 SetGroupColumn( i, false );
1425 }
1426
1427 std::set<wxString> seen;
1428 std::vector<wxString> order;
1429
1430 // Set columns that are present and shown
1431 for( const BOM_FIELD& field : aPreset.fieldsOrdered )
1432 {
1433 // Ignore empty fields
1434 if( !field.name || seen.count( field.name ) )
1435 continue;
1436
1437 seen.insert( field.name );
1438 order.emplace_back( field.name );
1439
1440 int col = GetFieldNameCol( field.name );
1441
1442 // Add any missing fields, if the user doesn't add any data
1443 // they won't be saved to the symbols anyway
1444 if( col == -1 )
1445 {
1446 AddColumn( field.name, field.label, true, aVariantName );
1447 col = GetFieldNameCol( field.name );
1448 }
1449 else
1450 {
1451 SetColLabelValue( col, field.label );
1452 }
1453
1454 SetGroupColumn( col, field.groupBy );
1455 SetShowColumn( col, field.show );
1456 }
1457
1458 // Set grouping columns
1460
1461 SetFieldsOrder( order );
1462
1463 // Set our sorting
1464 int sortCol = GetFieldNameCol( aPreset.sortField );
1465
1466 if( sortCol == -1 )
1468
1469 SetSorting( sortCol, aPreset.sortAsc );
1470
1471 SetFilter( aPreset.filterString );
1472 SetExcludeDNP( aPreset.excludeDNP );
1474 SetCurrentVariant( aVariantName );
1475
1476 RebuildRows();
1477}
1478
1479
1481{
1482 BOM_PRESET current;
1483 current.readOnly = false;
1484 current.fieldsOrdered = GetFieldsOrdered();
1485
1486 if( GetSortCol() >= 0 && GetSortCol() < GetNumberCols() )
1487 current.sortField = GetColFieldName( GetSortCol() );
1488
1489 current.sortAsc = GetSortAsc();
1490 current.filterString = GetFilter();
1491 current.groupSymbols = GetGroupingEnabled();
1492 current.excludeDNP = GetExcludeDNP();
1494
1495 return current;
1496}
1497
1498
1500{
1501 wxString out;
1502
1503 if( m_cols.empty() )
1504 return out;
1505
1506 int last_col = -1;
1507
1508 // Find the location for the line terminator
1509 for( size_t col = 0; col < m_cols.size(); col++ )
1510 {
1511 if( m_cols[col].m_show )
1512 last_col = static_cast<int>( col );
1513 }
1514
1515 // No shown columns
1516 if( last_col == -1 )
1517 return out;
1518
1519 auto formatField =
1520 [&]( wxString field, bool last ) -> wxString
1521 {
1522 if( !settings.keepLineBreaks )
1523 {
1524 field.Replace( wxS( "\r" ), wxS( "" ) );
1525 field.Replace( wxS( "\n" ), wxS( "" ) );
1526 }
1527
1528 if( !settings.keepTabs )
1529 {
1530 field.Replace( wxS( "\t" ), wxS( "" ) );
1531 }
1532
1533 if( !settings.stringDelimiter.IsEmpty() )
1534 {
1535 field.Replace( settings.stringDelimiter,
1536 settings.stringDelimiter + settings.stringDelimiter );
1537 }
1538
1539 return settings.stringDelimiter + field + settings.stringDelimiter
1540 + ( last ? wxString( wxS( "\n" ) ) : settings.fieldDelimiter );
1541 };
1542
1543 // Column names
1544 for( size_t col = 0; col < m_cols.size(); col++ )
1545 {
1546 if( !m_cols[col].m_show )
1547 continue;
1548
1549 out.Append( formatField( m_cols[col].m_label, col == static_cast<size_t>( last_col ) ) );
1550 }
1551
1552 // Data rows
1553 for( size_t row = 0; row < m_rows.size(); row++ )
1554 {
1555 // Don't output child rows
1556 if( GetRowFlags( static_cast<int>( row ) ) == CHILD_ITEM )
1557 continue;
1558
1559 for( size_t col = 0; col < m_cols.size(); col++ )
1560 {
1561 if( !m_cols[col].m_show )
1562 continue;
1563
1564 // Get the unannotated version of the field, e.g. no "> " or "v " by
1565 out.Append( formatField( GetExportValue( static_cast<int>( row ), static_cast<int>( col ),
1566 settings.refDelimiter, settings.refRangeDelimiter ),
1567 col == static_cast<size_t>( last_col ) ) );
1568 }
1569 }
1570
1571 return out;
1572}
1573
1574
1576{
1577 bool refListChanged = false;
1578
1579 for( const SCH_REFERENCE& ref : aRefs )
1580 {
1581 if( !m_symbolsList.Contains( ref ) )
1582 {
1583 SCH_SYMBOL* symbol = ref.GetSymbol();
1584
1585 m_symbolsList.AddItem( ref );
1586
1587 KIID_PATH key = makeDataStoreKey( ref.GetSheetPath(), *symbol );
1588
1589 // Update the fields of every reference
1590 for( const SCH_FIELD& field : symbol->GetFields() )
1591 {
1592 if( !field.IsPrivate() )
1593 {
1594 wxString name = field.GetCanonicalName();
1595 wxString value = symbol->Schematic()->ConvertKIIDsToRefs( field.GetText() );
1596
1597 m_dataStore[key][name] = value;
1598 }
1599 }
1600
1601 refListChanged = true;
1602 }
1603 }
1604
1605 if( refListChanged )
1606 m_symbolsList.SortBySymbolPtr();
1607}
1608
1609
1611{
1612 // The schematic event listener passes us the symbol after it has been removed,
1613 // so we can't just work with a SCH_REFERENCE_LIST like the other handlers as the
1614 // references are already gone. Instead we need to prune our list.
1615
1616 // Since we now use full KIID_PATH as keys, we need to find and remove all entries
1617 // that correspond to this symbol (their keys end with the symbol's UUID)
1618 KIID symbolUuid = aSymbol.m_Uuid;
1619 std::vector<KIID_PATH> keysToRemove;
1620
1621 for( const auto& [key, value] : m_dataStore )
1622 {
1623 if( !key.empty() && ( key.back() == symbolUuid ) )
1624 keysToRemove.push_back( key );
1625 }
1626
1627 for( const KIID_PATH& key : keysToRemove )
1628 m_dataStore.erase( key );
1629
1630 // Remove all refs that match this symbol using remove_if
1631 m_symbolsList.erase( std::remove_if( m_symbolsList.begin(), m_symbolsList.end(),
1632 [&aSymbol]( const SCH_REFERENCE& ref ) -> bool
1633 {
1634 return ref.GetSymbol()->m_Uuid == aSymbol.m_Uuid;
1635 } ),
1636 m_symbolsList.end() );
1637}
1638
1639
1641{
1642 for( const SCH_REFERENCE& ref : aRefs )
1643 {
1644 int index = m_symbolsList.FindRefByFullPath( ref.GetFullPath() );
1645
1646 if( index != -1 )
1647 {
1648 KIID_PATH key = makeDataStoreKey( ref.GetSheetPath(), *ref.GetSymbol() );
1649 m_dataStore.erase( key );
1650 m_symbolsList.RemoveItem( index );
1651 }
1652 }
1653}
1654
1655
1657 const wxString& aVariantName )
1658{
1659 bool refListChanged = false;
1660
1661 for( const SCH_REFERENCE& ref : aRefs )
1662 {
1663 // Update the fields of every reference. Do this by iterating through the data model
1664 // columns; we must have all fields in the symbol added to the data model at this point,
1665 // and some of the data model columns may be variables that are not present in the symbol
1666 for( const DATA_MODEL_COL& col : m_cols )
1667 updateDataStoreSymbolField( ref, col.m_fieldName, aVariantName );
1668
1669 if( SCH_REFERENCE* listRef = m_symbolsList.FindItem( ref ) )
1670 {
1671 *listRef = ref;
1672 }
1673 else
1674 {
1675 m_symbolsList.AddItem( ref );
1676 refListChanged = true;
1677 }
1678 }
1679
1680 if( refListChanged )
1681 m_symbolsList.SortBySymbolPtr();
1682}
1683
1684
1685bool FIELDS_EDITOR_GRID_DATA_MODEL::DeleteRows( size_t aPosition, size_t aNumRows )
1686{
1687 size_t curNumRows = m_rows.size();
1688
1689 if( aPosition >= curNumRows )
1690 {
1691 wxFAIL_MSG( wxString::Format( wxT( "Called FIELDS_EDITOR_GRID_DATA_MODEL::DeleteRows(aPosition=%lu, "
1692 "aNumRows=%lu)\nPosition value is invalid for present table with %lu rows" ),
1693 (unsigned long) aPosition, (unsigned long) aNumRows,
1694 (unsigned long) curNumRows ) );
1695
1696 return false;
1697 }
1698
1699 if( aNumRows > curNumRows - aPosition )
1700 {
1701 aNumRows = curNumRows - aPosition;
1702 }
1703
1704 if( aNumRows >= curNumRows )
1705 {
1706 m_rows.clear();
1707 m_dataStore.clear();
1708 }
1709 else
1710 {
1711 const auto first = m_rows.begin() + aPosition;
1712 std::vector<SCH_REFERENCE> dataMapRefs = first->m_Refs;
1713 m_rows.erase( first, first + aNumRows );
1714
1715 for( const SCH_REFERENCE& ref : dataMapRefs )
1716 m_dataStore.erase( ref.GetSheetPath().Path() );
1717 }
1718
1719 if( GetView() )
1720 {
1721 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aPosition, aNumRows );
1722 GetView()->ProcessTableMessage( msg );
1723 }
1724
1725 return true;
1726}
1727
1728
1729std::vector<FIELD_CASE_CONFLICT> DetectFieldCaseConflicts( const SCH_REFERENCE_LIST& aSymbols )
1730{
1731 std::vector<FIELD_CASE_CONFLICT> conflicts;
1732
1733 for( unsigned i = 0; i < aSymbols.GetCount(); ++i )
1734 {
1735 SCH_SYMBOL* symbol = aSymbols[i].GetSymbol();
1736
1737 if( !symbol )
1738 continue;
1739
1740 std::map<wxString, std::vector<std::pair<wxString, wxString>>> groups;
1741
1742 for( const SCH_FIELD& field : symbol->GetFields() )
1743 {
1744 if( field.IsMandatory() || field.IsPrivate() )
1745 continue;
1746
1747 groups[field.GetName().Lower()].emplace_back( field.GetName(), field.GetText() );
1748 }
1749
1750 for( const auto& [key, members] : groups )
1751 {
1752 if( members.size() < 2 )
1753 continue;
1754
1756 c.symbol = symbol;
1757 c.sheetPath = aSymbols[i].GetSheetPath();
1758 c.reference = symbol->GetRef( &c.sheetPath );
1759 c.caseFoldedKey = key;
1760 c.variants = members;
1761 conflicts.push_back( std::move( c ) );
1762 }
1763 }
1764
1765 return conflicts;
1766}
int index
const char * name
COMMIT & Modified(EDA_ITEM *aItem, EDA_ITEM *aCopy, BASE_SCREEN *aScreen=nullptr)
Create an undo entry for an item that has been already modified.
Definition commit.cpp:160
bool Find(const wxString &aTerm, int &aMatchersTriggered, int &aPosition)
Look in all existing matchers, return the earliest match of any of the existing.
const KIID m_Uuid
Definition eda_item.h:528
const EDA_ANGLE & GetTextAngle() const
Definition eda_text.h:172
void SetTextPos(const VECTOR2I &aPoint)
Definition eda_text.cpp:576
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:385
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition eda_text.cpp:298
int GetFieldNameCol(const wxString &aFieldName) const
void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser, const wxString &aVariantName)
std::vector< DATA_MODEL_ROW > m_rows
void SetFieldsOrder(const std::vector< wxString > &aNewOrder)
SCH_REFERENCE_LIST m_symbolsList
The flattened by hierarchy list of symbols.
wxGridCellRenderer * m_textVarRenderer
Renderer for cells with text variable references.
void UpdateReferences(const SCH_REFERENCE_LIST &aRefs, const wxString &aVariantName)
wxString m_currentVariant
Current variant name for highlighting.
bool DeleteRows(size_t aPosition=0, size_t aNumRows=1) override
bool groupMatch(const SCH_REFERENCE &lhRef, const SCH_REFERENCE &rhRef)
void updateDataStoreSymbolField(const SCH_REFERENCE &aSymbolRef, const wxString &aFieldName, const wxString &aVariantName)
bool unitMatch(const SCH_REFERENCE &lhRef, const SCH_REFERENCE &rhRef)
wxString getAttributeValue(const SCH_REFERENCE &aRef, const wxString &aAttributeName, const wxString &aVariantNames)
wxString GetExportValue(int aRow, int aCol, const wxString &refDelimiter, const wxString &refRangeDelimiter)
wxString getDefaultFieldValue(const SCH_REFERENCE &aRef, const wxString &aFieldName)
Get the default (non-variant) value for a field.
wxString getFieldShownText(const SCH_REFERENCE &aRef, const wxString &aFieldName)
wxString GetResolvedValue(int aRow, int aCol)
void RenameColumn(int aCol, const wxString &newName)
FIELDS_EDITOR_GRID_DATA_MODEL(const SCH_REFERENCE_LIST &aSymbolsList, wxGridCellAttr *aURLEditor)
bool IsExpanderColumn(int aCol) const override
wxString Export(const BOM_FMT_PRESET &settings)
std::vector< wxString > m_variantNames
Variant names for multi-variant DNP filtering.
std::vector< DATA_MODEL_COL > m_cols
void SetSorting(int aCol, bool ascending)
wxGridCellAttr * GetAttr(int aRow, int aCol, wxGridCellAttr::wxAttrKind aKind) override
void SetFilter(const wxString &aFilter)
bool setAttributeValue(SCH_REFERENCE &aRef, const wxString &aAttributeName, const wxString &aValue, const wxString &aVariantName=wxEmptyString)
Set the attribute value.
static bool cmp(const DATA_MODEL_ROW &lhGroup, const DATA_MODEL_ROW &rhGroup, FIELDS_EDITOR_GRID_DATA_MODEL *dataModel, int sortCol, bool ascending)
void ApplyBomPreset(const BOM_PRESET &preset, const wxString &aVariantName)
static const wxString ITEM_NUMBER_VARIABLE
void SetIncludeExcludedFromBOM(bool include)
bool isAttribute(const wxString &aFieldName)
wxString GetValue(int aRow, int aCol) override
static const wxString QUANTITY_VARIABLE
void SetGroupColumn(int aCol, bool group)
void RemoveSymbol(const SCH_SYMBOL &aSymbol)
std::vector< BOM_FIELD > GetFieldsOrdered()
void SetValue(int aRow, int aCol, const wxString &aValue) override
std::map< KIID_PATH, std::map< wxString, wxString > > m_dataStore
void ApplyData(SCH_COMMIT &aCommit, TEMPLATES &aTemplateFieldnames, const wxString &aVariantName)
void SetColLabelValue(int aCol, const wxString &aLabel) override
void SetCurrentVariant(const wxString &aVariantName)
Set the current variant name for highlighting purposes.
void RemoveReferences(const SCH_REFERENCE_LIST &aRefs)
void SetShowColumn(int aCol, bool show)
void AddReferences(const SCH_REFERENCE_LIST &aRefs)
Cell renderer that shows the expanded result of text variables (e.g.
wxSize GetBestSize(wxGrid &aGrid, wxGridCellAttr &aAttr, wxDC &aDC, int aRow, int aCol) override
wxGridCellRenderer * Clone() const override
void Draw(wxGrid &aGrid, wxGridCellAttr &aAttr, wxDC &aDC, const wxRect &aRect, int aRow, int aCol, bool isSelected) override
Definition kiid.h:48
wxString ConvertKIIDsToRefs(const wxString &aSource) const
wxString ConvertRefsToKIIDs(const wxString &aSource) const
bool IsMandatory() const
virtual const wxString & GetText() const override
Return the string associated with the text object.
Definition sch_field.h:126
FIELD_T GetId() const
Definition sch_field.h:130
wxString GetShownText(const SCH_SHEET_PATH *aPath, bool aAllowExtraText, int aDepth=0, const wxString &aVariantName=wxEmptyString) const
wxString GetName(bool aUseDefaultName=true) const
Return the field name (not translated).
void SetName(const wxString &aName)
void SetText(const wxString &aText) override
void SetPrivate(bool aPrivate)
Definition sch_item.h:253
SCHEMATIC * Schematic() const
Search the item hierarchy to find a SCHEMATIC.
Definition sch_item.cpp:272
bool IsPrivate() const
Definition sch_item.h:254
bool ResolveExcludedFromBOM(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const
Definition sch_item.cpp:318
bool ResolveDNP(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const
Definition sch_item.cpp:366
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
static wxString Shorthand(std::vector< SCH_REFERENCE > aList, const wxString &refDelimiter, const wxString &refRangeDelimiter)
Return a shorthand string representing all the references in the list.
A helper to define a symbol's reference designator in a schematic.
void SetSymbolExcludedFromBoard(bool aEnable)
const SCH_SHEET_PATH & GetSheetPath() const
void SetSymbolExcludedFromBOM(bool aEnable, const wxString &aVariant=wxEmptyString)
bool GetSymbolExcludedFromBOM(const wxString &aVariant=wxEmptyString) const
bool GetSymbolDNP(const wxString &aVariant=wxEmptyString) const
SCH_SYMBOL * GetSymbol() const
void SetSymbolExcludedFromSim(bool aEnable, const wxString &aVariant=wxEmptyString)
wxString GetRef() const
bool GetSymbolExcludedFromBoard() const
wxString GetFullRef(bool aIncludeUnit=true) const
Return reference name with unit altogether.
bool GetSymbolExcludedFromSim(const wxString &aVariant=wxEmptyString) const
bool IsMultiUnit() const
wxString GetRefNumber() const
void SetSymbolDNP(bool aEnable, const wxString &aVariant=wxEmptyString)
int GetRefCount() const
Definition sch_screen.h:172
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
bool GetExcludedFromBOM() const
KIID_PATH Path() const
Get the sheet path as an KIID_PATH.
bool IsContainedWithin(const SCH_SHEET_PATH &aSheetPathToTest) const
Check if this path is contained inside aSheetPathToTest.
bool GetDNP() const
Schematic symbol object.
Definition sch_symbol.h:76
SCH_FIELD * FindFieldCaseInsensitive(const wxString &aFieldName)
Search for a SCH_FIELD with aFieldName.
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly) const override
Populate a std::vector with SCH_FIELDs, sorted in ordinal order.
VECTOR2I GetPosition() const override
Definition sch_symbol.h:892
bool ResolveTextVar(const SCH_SHEET_PATH *aPath, wxString *token, int aDepth=0) const
Resolve any references to system tokens supported by the symbol.
SCH_FIELD * AddField(const SCH_FIELD &aField)
Add a field to the symbol.
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
const TEMPLATE_FIELDNAME * GetFieldName(const wxString &aName)
Search for aName in the template field name list.
void SetValueAsBool(int aRow, int aCol, bool aValue) override
wxString GetColLabelValue(int aCol) override
std::vector< BOM_FIELD > m_fields
void SetValue(int aRow, int aCol, const wxString &aValue) override
void AppendRow(const wxString &aFieldName, const wxString &aBOMName, bool aShow, bool aGroupBy)
bool GetValueAsBool(int aRow, int aCol) override
void SetCanonicalFieldName(int aRow, const wxString &aName)
wxString GetValue(int aRow, int aCol) override
wxGridCellAttr * enhanceAttr(wxGridCellAttr *aInputAttr, int aRow, int aCol, wxGridCellAttr::wxAttrKind aKind)
Definition wx_grid.cpp:47
std::map< int, wxGridCellAttr * > m_colAttrs
Definition wx_grid.h:93
wxGridCellAttr * GetAttr(int aRow, int aCol, wxGridCellAttr::wxAttrKind aKind) override
Definition wx_grid.h:68
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, int aFlags)
Definition common.cpp:63
bool IsGeneratedField(const wxString &aSource)
Returns true if the string is generated, e.g contains a single text var reference.
Definition common.cpp:460
The common library.
#define _(s)
@ CTX_SEARCH
std::vector< FIELD_CASE_CONFLICT > DetectFieldCaseConflicts(const SCH_REFERENCE_LIST &aSymbols)
static KIID_PATH makeDataStoreKey(const SCH_SHEET_PATH &aSheetPath, const SCH_SYMBOL &aSymbol)
Create a unique key for the data store by combining the KIID_PATH from the SCH_SHEET_PATH with the sy...
#define LABEL_COLUMN
#define DISPLAY_NAME_COLUMN
#define GROUP_BY_COLUMN
#define SHOW_FIELD_COLUMN
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
int StrNumCmp(const wxString &aString1, const wxString &aString2, bool aIgnoreCase)
Compare two strings with alphanumerical content.
int ValueStringCompare(const wxString &strFWord, const wxString &strSWord)
Compare strings like the strcmp function but handle numbers and modifiers within the string text corr...
bool IsURL(wxString aStr)
Performs a URL sniff-test on a string.
wxString label
wxString name
wxString fieldDelimiter
wxString stringDelimiter
wxString refRangeDelimiter
wxString refDelimiter
wxString sortField
bool groupSymbols
std::vector< BOM_FIELD > fieldsOrdered
bool includeExcludedFromBOM
bool excludeDNP
wxString filterString
std::vector< SCH_REFERENCE > m_Refs
SCH_SHEET_PATH sheetPath
std::vector< std::pair< wxString, wxString > > variants
Hold a name of a symbol's field, field value, and default visibility.
wxString GetDefaultFieldName(FIELD_T aFieldId, bool aTranslateForHI)
Return a default symbol field name for a mandatory field type.
#define DO_TRANSLATE
#define MANDATORY_FIELDS
FIELD_T
The set of all field indices assuming an array like sequence that a SCH_COMPONENT or LIB_PART can hol...
@ USER
The field ID hasn't been set yet; field is invalid.
@ DATASHEET
name of datasheet
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
wxString GetCanonicalFieldName(FIELD_T aFieldType)
std::string path
KIBIS_MODEL * model
#define kv
#define INDETERMINATE_STATE
Used for holding indeterminate values, such as with multiple selections holding different values or c...
Definition ui_common.h:46
GROUP_COLLAPSED_DURING_SORT
Definition wx_grid.h:47
GROUP_COLLAPSED
Definition wx_grid.h:46
GROUP_EXPANDED
Definition wx_grid.h:48
GROUP_SINGLETON
Definition wx_grid.h:45