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