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
367 || IsURL( GetValue( m_rows[aRow], aCol ) ) )
368 {
369 if( m_urlEditor )
370 {
371 m_urlEditor->IncRef();
372 attr = m_urlEditor;
373 }
374 }
375
376 // Highlight cells that differ from default when viewing a variant
377 if( !m_currentVariant.IsEmpty() && aRow >= 0 && aRow < (int) m_rows.size()
378 && aCol >= 0 && aCol < (int) m_cols.size() )
379 {
380 const wxString& fieldName = m_cols[aCol].m_fieldName;
381
382 // Skip Reference and generated fields (like ${QUANTITY}) for highlighting
383 if( !ColIsReference( aCol ) && !ColIsQuantity( aCol ) && !ColIsItemNumber( aCol ) )
384 {
385 const DATA_MODEL_ROW& row = m_rows[aRow];
386
387 // Check if any symbol in this row has a variant-specific value
388 bool hasVariantDifference = false;
389
390 for( const SCH_REFERENCE& ref : row.m_Refs )
391 {
392 wxString defaultValue = getDefaultFieldValue( ref, fieldName );
393
394 KIID_PATH symbolKey = KIID_PATH();
395
396 if( const SCH_SYMBOL* symbol = ref.GetSymbol() )
397 {
398 symbolKey = ref.GetSheetPath().Path();
399 symbolKey.push_back( symbol->m_Uuid );
400 }
401
402 // Get the current value from the data store
403 wxString currentValue;
404
405 if( m_dataStore.contains( symbolKey ) && m_dataStore[symbolKey].contains( fieldName ) )
406 currentValue = m_dataStore[symbolKey][fieldName];
407
408 if( currentValue != defaultValue )
409 {
410 hasVariantDifference = true;
411 break;
412 }
413 }
414
415 if( hasVariantDifference )
416 {
417 if( !attr )
418 {
419 attr = new wxGridCellAttr();
420 }
421 else
422 {
423 // Clone so we don't modify the shared URL editor attr
424 wxGridCellAttr* newAttr = attr->Clone();
425 attr->DecRef();
426 attr = newAttr;
427 }
428
429 // Use a subtle highlight color that works in both light and dark themes
430 // Light yellow in light mode, darker yellow-ish in dark mode
431 wxColour bg = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW );
432 bool isDark = ( bg.Red() + bg.Green() + bg.Blue() ) < 384;
433
434 if( isDark )
435 attr->SetBackgroundColour( wxColour( 80, 80, 40 ) ); // Dark gold/brown
436 else
437 attr->SetBackgroundColour( wxColour( 255, 255, 200 ) ); // Light yellow
438 }
439 }
440 }
441
442 if( attr )
443 return enhanceAttr( attr, aRow, aCol, aKind );
444
445 return WX_GRID_TABLE_BASE::GetAttr( aRow, aCol, aKind );
446}
447
448
450 const wxString& refDelimiter,
451 const wxString& refRangeDelimiter,
452 bool resolveVars,
453 bool listMixedValues )
454{
455 std::vector<SCH_REFERENCE> references;
456 std::set<wxString> mixedValues;
457 wxString fieldValue;
458
459 for( const SCH_REFERENCE& ref : group.m_Refs )
460 {
461 if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
462 {
463 references.push_back( ref );
464 }
465 else // Other columns are either a single value or ROW_MULTI_ITEMS
466 {
467 KIID_PATH symbolKey = makeDataStoreKey( ref.GetSheetPath(), *ref.GetSymbol() );
468
469 if( !m_dataStore.contains( symbolKey ) || !m_dataStore[symbolKey].contains( m_cols[aCol].m_fieldName ) )
470 return INDETERMINATE_STATE;
471
472 wxString refFieldValue = m_dataStore[symbolKey][m_cols[aCol].m_fieldName];
473
474 if( resolveVars )
475 {
476 if( IsGeneratedField( m_cols[aCol].m_fieldName ) )
477 {
478 // Generated fields (e.g. ${QUANTITY}) can't have un-applied values as they're
479 // read-only. Resolve them against the field.
480 refFieldValue = getFieldShownText( ref, m_cols[aCol].m_fieldName );
481 }
482 else if( refFieldValue.Contains( wxT( "${" ) ) )
483 {
484 // Resolve variables in the un-applied value using the parent symbol and instance
485 // data.
486 std::function<bool( wxString* )> symbolResolver =
487 [&]( wxString* token ) -> bool
488 {
489 return ref.GetSymbol()->ResolveTextVar( &ref.GetSheetPath(), token );
490 };
491
492 refFieldValue = ExpandTextVars( refFieldValue, & symbolResolver );
493 }
494 }
495
496 if( listMixedValues )
497 mixedValues.insert( refFieldValue );
498 else if( &ref == &group.m_Refs.front() )
499 fieldValue = refFieldValue;
500 else if( fieldValue != refFieldValue )
501 return INDETERMINATE_STATE;
502 }
503 }
504
505 if( listMixedValues )
506 {
507 fieldValue = wxEmptyString;
508
509 for( const wxString& value : mixedValues )
510 {
511 if( value.IsEmpty() )
512 continue;
513 else if( fieldValue.IsEmpty() )
514 fieldValue = value;
515 else
516 fieldValue += "," + value;
517 }
518 }
519
520 if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
521 {
522 // Remove duplicates (other units of multi-unit parts)
523 std::sort( references.begin(), references.end(),
524 []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
525 {
526 wxString l_ref( l.GetRef() << l.GetRefNumber() );
527 wxString r_ref( r.GetRef() << r.GetRefNumber() );
528 return StrNumCmp( l_ref, r_ref, true ) < 0;
529 } );
530
531 auto logicalEnd = std::unique( references.begin(), references.end(),
532 []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
533 {
534 // If unannotated then we can't tell what units belong together
535 // so we have to leave them all
536 if( l.GetRefNumber() == wxT( "?" ) )
537 return false;
538
539 wxString l_ref( l.GetRef() << l.GetRefNumber() );
540 wxString r_ref( r.GetRef() << r.GetRefNumber() );
541 return l_ref == r_ref;
542 } );
543
544 references.erase( logicalEnd, references.end() );
545 }
546
547 if( ColIsReference( aCol ) )
548 fieldValue = SCH_REFERENCE_LIST::Shorthand( references, refDelimiter, refRangeDelimiter );
549 else if( ColIsQuantity( aCol ) )
550 fieldValue = wxString::Format( wxT( "%d" ), (int) references.size() );
551 else if( ColIsItemNumber( aCol ) && group.m_Flag != CHILD_ITEM )
552 fieldValue = wxString::Format( wxT( "%d" ), group.m_ItemNumber );
553
554 return fieldValue;
555}
556
557
558void FIELDS_EDITOR_GRID_DATA_MODEL::SetValue( int aRow, int aCol, const wxString& aValue )
559{
560 wxCHECK_RET( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), wxS( "Invalid column number" ) );
561
562 // Can't modify references or generated fields (e.g. ${QUANTITY})
563 if( ColIsReference( aCol )
564 || ( IsGeneratedField( m_cols[aCol].m_fieldName ) && !ColIsAttribute( aCol ) ) )
565 {
566 return;
567 }
568
569 DATA_MODEL_ROW& rowGroup = m_rows[aRow];
570
571 const SCH_SYMBOL* sharedSymbol = nullptr;
572 bool isSharedInstance = false;
573
574 for( const SCH_REFERENCE& ref : rowGroup.m_Refs )
575 {
576 const SCH_SCREEN* screen = nullptr;
577
578 // Check to see if the symbol associated with this row has more than one instance.
579 if( const SCH_SYMBOL* symbol = ref.GetSymbol() )
580 {
581 screen = static_cast<const SCH_SCREEN*>( symbol->GetParent() );
582
583 isSharedInstance = ( screen && ( screen->GetRefCount() > 1 ) );
584 sharedSymbol = symbol;
585 }
586
587 KIID_PATH key = makeDataStoreKey( ref.GetSheetPath(), *ref.GetSymbol() );
588 m_dataStore[key][m_cols[aCol].m_fieldName] = aValue;
589 }
590
591 // Update all of the other instances for the shared symbol as required.
592 if( isSharedInstance
593 && ( ( rowGroup.m_Flag == GROUP_SINGLETON ) || ( rowGroup.m_Flag == CHILD_ITEM ) ) )
594 {
595 for( DATA_MODEL_ROW& row : m_rows )
596 {
597 if( row.m_ItemNumber == aRow + 1 )
598 continue;
599
600 for( const SCH_REFERENCE& ref : row.m_Refs )
601 {
602 if( ref.GetSymbol() != sharedSymbol )
603 continue;
604
605 KIID_PATH key = makeDataStoreKey( ref.GetSheetPath(), *ref.GetSymbol() );
606 m_dataStore[key][m_cols[aCol].m_fieldName] = aValue;
607 }
608 }
609 }
610
611 m_edited = true;
612}
613
614
616{
617 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
618 return m_cols[aCol].m_fieldName == GetCanonicalFieldName( FIELD_T::REFERENCE );
619}
620
621
623{
624 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
625 return m_cols[aCol].m_fieldName == GetCanonicalFieldName( FIELD_T::VALUE );
626}
627
628
630{
631 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
632 return m_cols[aCol].m_fieldName == QUANTITY_VARIABLE;
633}
634
635
637{
638 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
639 return m_cols[aCol].m_fieldName == ITEM_NUMBER_VARIABLE;
640}
641
642
644{
645 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
646 return isAttribute( m_cols[aCol].m_fieldName );
647}
648
649
651 const DATA_MODEL_ROW& rhGroup,
652 FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol,
653 bool ascending )
654{
655 // Empty rows always go to the bottom, whether ascending or descending
656 if( lhGroup.m_Refs.size() == 0 )
657 return true;
658 else if( rhGroup.m_Refs.size() == 0 )
659 return false;
660
661 // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
662 // to get the opposite sort. i.e. ~(a<b) != (a>b)
663 auto local_cmp =
664 [ ascending ]( const auto a, const auto b )
665 {
666 if( ascending )
667 return a < b;
668 else
669 return a > b;
670 };
671
672 // Primary sort key is sortCol; secondary is always REFERENCE (column 0)
673 if( sortCol < 0 || sortCol >= dataModel->GetNumberCols() )
674 sortCol = 0;
675
676 wxString lhs = dataModel->GetValue( lhGroup, sortCol ).Trim( true ).Trim( false );
677 wxString rhs = dataModel->GetValue( rhGroup, sortCol ).Trim( true ).Trim( false );
678
679 if( lhs == rhs || dataModel->ColIsReference( sortCol ) )
680 {
681 wxString lhRef = lhGroup.m_Refs[0].GetRef() + lhGroup.m_Refs[0].GetRefNumber();
682 wxString rhRef = rhGroup.m_Refs[0].GetRef() + rhGroup.m_Refs[0].GetRefNumber();
683 return local_cmp( StrNumCmp( lhRef, rhRef, true ), 0 );
684 }
685 else
686 {
687 return local_cmp( ValueStringCompare( lhs, rhs ), 0 );
688 }
689}
690
691
693{
695
696 // We're going to sort the rows based on their first reference, so the first reference
697 // had better be the lowest one.
698 for( DATA_MODEL_ROW& row : m_rows )
699 {
700 std::sort( row.m_Refs.begin(), row.m_Refs.end(),
701 []( const SCH_REFERENCE& lhs, const SCH_REFERENCE& rhs )
702 {
703 wxString lhs_ref( lhs.GetRef() << lhs.GetRefNumber() );
704 wxString rhs_ref( rhs.GetRef() << rhs.GetRefNumber() );
705 return StrNumCmp( lhs_ref, rhs_ref, true ) < 0;
706 } );
707 }
708
709 std::sort( m_rows.begin(), m_rows.end(),
710 [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
711 {
712 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
713 } );
714
715 // Time to renumber the item numbers
716 int itemNumber = 1;
717
718 for( DATA_MODEL_ROW& row : m_rows )
719 {
720 row.m_ItemNumber = itemNumber++;
721 }
722
724}
725
726
728{
729 // If items are unannotated then we can't tell if they're units of the same symbol or not
730 if( lhRef.GetRefNumber() == wxT( "?" ) )
731 return false;
732
733 return ( lhRef.GetRef() == rhRef.GetRef() && lhRef.GetRefNumber() == rhRef.GetRefNumber() );
734}
735
736
738{
740 bool matchFound = false;
741
742 if( refCol == -1 )
743 return false;
744
745 // First check the reference column. This can be done directly out of the
746 // SCH_REFERENCEs as the references can't be edited in the grid.
747 if( m_cols[refCol].m_group )
748 {
749 // if we're grouping by reference, then only the prefix must match
750 if( lhRef.GetRef() != rhRef.GetRef() )
751 return false;
752
753 matchFound = true;
754 }
755
756 KIID_PATH lhRefKey = makeDataStoreKey( lhRef.GetSheetPath(), *lhRef.GetSymbol() );
757 KIID_PATH rhRefKey = makeDataStoreKey( rhRef.GetSheetPath(), *rhRef.GetSymbol() );
758
759 // Now check all the other columns.
760 for( size_t i = 0; i < m_cols.size(); ++i )
761 {
762 //Handled already
763 if( static_cast<int>( i ) == refCol )
764 continue;
765
766 if( !m_cols[i].m_group )
767 continue;
768
769 // If the field is generated (e.g. ${QUANTITY}), we need to resolve it through the symbol
770 // to get the actual current value; otherwise we need to pull it out of the store so the
771 // refresh can regroup based on values that haven't been applied to the schematic yet.
772 wxString lh, rh;
773
774 if( IsGeneratedField( m_cols[i].m_fieldName )
775 || IsGeneratedField( m_dataStore[lhRefKey][m_cols[i].m_fieldName] ) )
776 {
777 lh = getFieldShownText( lhRef, m_cols[i].m_fieldName );
778 }
779 else
780 {
781 lh = m_dataStore[lhRefKey][m_cols[i].m_fieldName];
782 }
783
784 if( IsGeneratedField( m_cols[i].m_fieldName )
785 || IsGeneratedField( m_dataStore[rhRefKey][m_cols[i].m_fieldName] ) )
786 {
787 rh = getFieldShownText( rhRef, m_cols[i].m_fieldName );
788 }
789 else
790 {
791 rh = m_dataStore[rhRefKey][m_cols[i].m_fieldName];
792 }
793
794 if( lh != rh )
795 return false;
796
797 matchFound = true;
798 }
799
800 return matchFound;
801}
802
803
805 const wxString& aFieldName )
806{
807 SCH_FIELD* field = aRef.GetSymbol()->GetField( aFieldName );
808
809 if( field )
810 {
811 if( field->IsPrivate() )
812 return wxEmptyString;
813 else
814 return field->GetShownText( &aRef.GetSheetPath(), false );
815 }
816
817 // Handle generated fields with variables as names (e.g. ${QUANTITY}) that are not present in
818 // the symbol by giving them the correct value by resolving against the symbol
819 if( IsGeneratedField( aFieldName ) )
820 {
821 int depth = 0;
822 const SCH_SHEET_PATH& path = aRef.GetSheetPath();
823
824 std::function<bool( wxString* )> symbolResolver =
825 [&]( wxString* token ) -> bool
826 {
827 return aRef.GetSymbol()->ResolveTextVar( &path, token, depth + 1 );
828 };
829
830 return ExpandTextVars( aFieldName, &symbolResolver );
831 }
832
833 return wxEmptyString;
834}
835
836
837bool FIELDS_EDITOR_GRID_DATA_MODEL::isAttribute( const wxString& aFieldName )
838{
839 return aFieldName == wxS( "${DNP}" )
840 || aFieldName == wxS( "${EXCLUDE_FROM_BOARD}" )
841 || aFieldName == wxS( "${EXCLUDE_FROM_BOM}" )
842 || aFieldName == wxS( "${EXCLUDE_FROM_SIM}" );
843}
844
845
846wxString FIELDS_EDITOR_GRID_DATA_MODEL::getAttributeValue( const SCH_REFERENCE& aRef, const wxString& aAttributeName,
847 const wxString& aVariantName )
848{
849 if( aAttributeName == wxS( "${DNP}" ) )
850 return aRef.GetSymbolDNP( aVariantName ) ? wxS( "1" ) : wxS( "0" );
851
852 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
853 return aRef.GetSymbolExcludedFromBoard() ? wxS( "1" ) : wxS( "0" );
854
855 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
856 return aRef.GetSymbolExcludedFromBOM( aVariantName ) ? wxS( "1" ) : wxS( "0" );
857
858 if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
859 return aRef.GetSymbolExcludedFromSim( aVariantName ) ? wxS( "1" ) : wxS( "0" );
860
861 return wxS( "0" );
862}
863
864
866 const wxString& aFieldName )
867{
868 const SCH_SYMBOL* symbol = aRef.GetSymbol();
869
870 if( !symbol )
871 return wxEmptyString;
872
873 // For attributes, get the default (non-variant) value
874 if( isAttribute( aFieldName ) )
875 return getAttributeValue( aRef, aFieldName, wxEmptyString );
876
877 // For regular fields, get the text without variant override
878 if( const SCH_FIELD* field = symbol->GetField( aFieldName ) )
879 {
880 if( field->IsPrivate() )
881 return wxEmptyString;
882
883 // Get the field text with empty variant name (default value)
884 wxString value = symbol->Schematic()->ConvertKIIDsToRefs(
885 field->GetText( &aRef.GetSheetPath(), wxEmptyString ) );
886 return value;
887 }
888
889 // For generated fields, return the field name itself
890 if( IsGeneratedField( aFieldName ) )
891 return aFieldName;
892
893 return wxEmptyString;
894}
895
896
898 const wxString& aAttributeName,
899 const wxString& aValue,
900 const wxString& aVariantName )
901{
902 bool attrChanged = false;
903 bool newValue = aValue == wxS( "1" );
904
905 if( aAttributeName == wxS( "${DNP}" ) )
906 {
907 attrChanged = aRef.GetSymbolDNP( aVariantName ) != newValue;
908 aRef.SetSymbolDNP( newValue, aVariantName );
909 }
910 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
911 {
912 attrChanged = aRef.GetSymbolExcludedFromBoard() != newValue;
913 aRef.SetSymbolExcludedFromBoard( newValue );
914 }
915 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
916 {
917 attrChanged = aRef.GetSymbolExcludedFromBOM( aVariantName ) != newValue;
918 aRef.SetSymbolExcludedFromBOM( newValue, aVariantName );
919 }
920 else if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
921 {
922 attrChanged = aRef.GetSymbolExcludedFromSim( aVariantName ) != newValue;
923 aRef.SetSymbolExcludedFromSim( newValue, aVariantName );
924 }
925
926 return attrChanged;
927}
928
929
934
935
940
941
943{
944 if( !m_rebuildsEnabled )
945 return;
946
947 if( GetView() )
948 {
949 // Commit any pending in-place edits before the row gets moved out from under
950 // the editor.
951 static_cast<WX_GRID*>( GetView() )->CommitPendingChanges( true );
952
953 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
954 GetView()->ProcessTableMessage( msg );
955 }
956
957 m_rows.clear();
958
959 EDA_COMBINED_MATCHER matcher( m_filter.Lower(), CTX_SEARCH );
960
961 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
962 {
964
965 if( !m_filter.IsEmpty() && !matcher.Find( ref.GetFullRef().Lower() ) )
966 continue;
967
968 if( m_excludeDNP )
969 {
970 bool isDNP = false;
971
972 if( !m_variantNames.empty() )
973 {
974 for( const wxString& variantName : m_variantNames )
975 {
976 if( ref.GetSymbol()->GetDNP( &ref.GetSheetPath(), variantName )
977 || ref.GetSheetPath().GetDNP( variantName ) )
978 {
979 isDNP = true;
980 break;
981 }
982 }
983 }
984 else
985 {
986 isDNP = ref.GetSymbol()->GetDNP( &ref.GetSheetPath(), m_currentVariant )
988 }
989
990 if( isDNP )
991 continue;
992 }
993
994 if( !m_includeExcluded )
995 {
996 bool isExcluded = false;
997
998 if( !m_variantNames.empty() )
999 {
1000 for( const wxString& variantName : m_variantNames )
1001 {
1002 if( ref.GetSymbol()->GetExcludedFromBOM( &ref.GetSheetPath(), variantName )
1003 || ref.GetSheetPath().GetExcludedFromBOM( variantName ) )
1004 {
1005 isExcluded = true;
1006 break;
1007 }
1008 }
1009 }
1010 else
1011 {
1012 isExcluded = ref.GetSymbol()->GetExcludedFromBOM( &ref.GetSheetPath(), m_currentVariant )
1014 }
1015
1016 if( isExcluded )
1017 continue;
1018 }
1019
1020 // Check if the symbol if on the current sheet or, in the sheet path somewhere
1021 // depending on scope
1022 if( ( m_scope == SCOPE::SCOPE_SHEET && ref.GetSheetPath() != m_path )
1023 || ( m_scope == SCOPE::SCOPE_SHEET_RECURSIVE
1024 && !ref.GetSheetPath().IsContainedWithin( m_path ) ) )
1025 {
1026 continue;
1027 }
1028
1029 bool matchFound = false;
1030
1031 // Performance optimization for ungrouped case to skip the N^2 for loop
1032 if( !m_groupingEnabled && !ref.IsMultiUnit() )
1033 {
1034 m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
1035 continue;
1036 }
1037
1038 // See if we already have a row which this symbol fits into
1039 for( DATA_MODEL_ROW& row : m_rows )
1040 {
1041 // all group members must have identical refs so just use the first one
1042 SCH_REFERENCE rowRef = row.m_Refs[0];
1043
1044 if( unitMatch( ref, rowRef ) )
1045 {
1046 matchFound = true;
1047 row.m_Refs.push_back( ref );
1048 break;
1049 }
1050 else if( m_groupingEnabled && groupMatch( ref, rowRef ) )
1051 {
1052 matchFound = true;
1053 row.m_Refs.push_back( ref );
1054 row.m_Flag = GROUP_COLLAPSED;
1055 break;
1056 }
1057 }
1058
1059 if( !matchFound )
1060 m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
1061 }
1062
1063 if( GetView() )
1064 {
1065 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() );
1066 GetView()->ProcessTableMessage( msg );
1067 }
1068
1069 Sort();
1070}
1071
1072
1074{
1075 std::vector<DATA_MODEL_ROW> children;
1076
1077 for( SCH_REFERENCE& ref : m_rows[aRow].m_Refs )
1078 {
1079 bool matchFound = false;
1080
1081 // See if we already have a child group which this symbol fits into
1082 for( DATA_MODEL_ROW& child : children )
1083 {
1084 // group members are by definition all matching, so just check
1085 // against the first member
1086 if( unitMatch( ref, child.m_Refs[0] ) )
1087 {
1088 matchFound = true;
1089 child.m_Refs.push_back( ref );
1090 break;
1091 }
1092 }
1093
1094 if( !matchFound )
1095 children.emplace_back( DATA_MODEL_ROW( ref, CHILD_ITEM ) );
1096 }
1097
1098 if( children.size() < 2 )
1099 return;
1100
1101 std::sort( children.begin(), children.end(),
1102 [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
1103 {
1104 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
1105 } );
1106
1107 m_rows[aRow].m_Flag = GROUP_EXPANDED;
1108 m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() );
1109
1110 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, children.size() );
1111 GetView()->ProcessTableMessage( msg );
1112}
1113
1114
1116{
1117 auto firstChild = m_rows.begin() + aRow + 1;
1118 auto afterLastChild = firstChild;
1119 int deleted = 0;
1120
1121 while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM )
1122 {
1123 deleted++;
1124 afterLastChild++;
1125 }
1126
1127 m_rows[aRow].m_Flag = GROUP_COLLAPSED;
1128 m_rows.erase( firstChild, afterLastChild );
1129
1130 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted );
1131 GetView()->ProcessTableMessage( msg );
1132}
1133
1134
1136{
1137 DATA_MODEL_ROW& group = m_rows[aRow];
1138
1139 if( group.m_Flag == GROUP_COLLAPSED )
1140 ExpandRow( aRow );
1141 else if( group.m_Flag == GROUP_EXPANDED )
1142 CollapseRow( aRow );
1143}
1144
1145
1147{
1148 for( size_t i = 0; i < m_rows.size(); ++i )
1149 {
1150 if( m_rows[i].m_Flag == GROUP_EXPANDED )
1151 {
1152 CollapseRow( i );
1154 }
1155 }
1156}
1157
1158
1160{
1161 for( size_t i = 0; i < m_rows.size(); ++i )
1162 {
1163 if( m_rows[i].m_Flag == GROUP_COLLAPSED_DURING_SORT )
1164 ExpandRow( i );
1165 }
1166}
1167
1168
1170 const wxString& aVariantName )
1171{
1172 bool symbolModified = false;
1173 std::unique_ptr<SCH_SYMBOL> symbolCopy;
1174
1175 for( size_t i = 0; i < m_symbolsList.GetCount(); i++ )
1176 {
1177 SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol();
1178 SCH_SYMBOL* nextSymbol = nullptr;
1179
1180 if( ( i + 1 ) < m_symbolsList.GetCount() )
1181 nextSymbol = m_symbolsList[i + 1].GetSymbol();
1182
1183 if( i == 0 )
1184 symbolCopy = std::make_unique<SCH_SYMBOL>( *symbol );
1185
1186 KIID_PATH key = makeDataStoreKey( m_symbolsList[i].GetSheetPath(), *symbol );
1187 const std::map<wxString, wxString>& fieldStore = m_dataStore[key];
1188
1189 for( const auto& [srcName, srcValue] : fieldStore )
1190 {
1191 // Attributes bypass the field logic, so handle them first
1192 if( isAttribute( srcName ) )
1193 {
1194 symbolModified |= setAttributeValue( m_symbolsList[i], srcName, srcValue, aVariantName );
1195 continue;
1196 }
1197
1198 // Skip generated fields with variables as names (e.g. ${QUANTITY});
1199 // they can't be edited
1200 if( IsGeneratedField( srcName ) )
1201 continue;
1202
1203 SCH_FIELD* destField = symbol->GetField( srcName );
1204
1205 if( destField && destField->IsPrivate() )
1206 {
1207 if( srcValue.IsEmpty() )
1208 continue;
1209 else
1210 destField->SetPrivate( false );
1211 }
1212
1213 int col = GetFieldNameCol( srcName );
1214 bool userAdded = ( col != -1 && m_cols[col].m_userAdded );
1215
1216 // Add a not existing field if it has a value for this symbol
1217 bool createField = !destField && ( !srcValue.IsEmpty() || userAdded );
1218
1219 if( createField )
1220 {
1221 destField = symbol->AddField( SCH_FIELD( symbol, FIELD_T::USER, srcName ) );
1222 destField->SetTextAngle( symbol->GetField( FIELD_T::REFERENCE )->GetTextAngle() );
1223
1224 if( const TEMPLATE_FIELDNAME* srcTemplate = aTemplateFieldnames.GetFieldName( srcName ) )
1225 destField->SetVisible( srcTemplate->m_Visible );
1226 else
1227 destField->SetVisible( false );
1228
1229 destField->SetTextPos( symbol->GetPosition() );
1230 symbolModified = true;
1231 }
1232
1233 if( !destField )
1234 continue;
1235
1236 // Reference is not editable from this dialog
1237 if( destField->GetId() == FIELD_T::REFERENCE )
1238 continue;
1239
1240 wxString previousValue = destField->GetText( &m_symbolsList[i].GetSheetPath(), aVariantName );
1241
1242 destField->SetText( symbol->Schematic()->ConvertRefsToKIIDs( srcValue ), &m_symbolsList[i].GetSheetPath(),
1243 aVariantName );
1244
1245 if( !createField && ( previousValue != srcValue ) )
1246 symbolModified = true;
1247 }
1248
1249 for( int ii = static_cast<int>( symbol->GetFields().size() ) - 1; ii >= 0; ii-- )
1250 {
1251 if( symbol->GetFields()[ii].IsMandatory() || symbol->GetFields()[ii].IsPrivate() )
1252 continue;
1253
1254 if( fieldStore.count( symbol->GetFields()[ii].GetName() ) == 0 )
1255 {
1256 symbol->GetFields().erase( symbol->GetFields().begin() + ii );
1257 symbolModified = true;
1258 }
1259 }
1260
1261 if( symbolModified && ( symbol != nextSymbol ) )
1262 aCommit.Modified( symbol, symbolCopy.release(), m_symbolsList[i].GetSheetPath().LastScreen() );
1263
1264 // Only reset the modified flag and next symbol copy if the next symbol is different from the current one.
1265 if( symbol != nextSymbol )
1266 {
1267 if( nextSymbol )
1268 symbolCopy = std::make_unique<SCH_SYMBOL>( *nextSymbol );
1269 else
1270 symbolCopy.reset( nullptr );
1271
1272 symbolModified = false;
1273 }
1274 }
1275
1276 m_edited = false;
1277}
1278
1279
1281{
1282 int width = 0;
1283
1284 if( ColIsReference( aCol ) )
1285 {
1286 for( int row = 0; row < GetNumberRows(); ++row )
1287 width = std::max( width, KIUI::GetTextSize( GetValue( row, aCol ), GetView() ).x );
1288 }
1289 else
1290 {
1291 wxString fieldName = GetColFieldName( aCol ); // symbol fieldName or Qty string
1292
1293 for( unsigned symbolRef = 0; symbolRef < m_symbolsList.GetCount(); ++symbolRef )
1294 {
1295 KIID_PATH key = makeDataStoreKey( m_symbolsList[symbolRef].GetSheetPath(),
1296 *m_symbolsList[symbolRef].GetSymbol() );
1297 wxString text = m_dataStore[key][fieldName];
1298
1299 width = std::max( width, KIUI::GetTextSize( text, GetView() ).x );
1300 }
1301 }
1302
1303 return width;
1304}
1305
1306
1307void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyBomPreset( const BOM_PRESET& aPreset, const wxString& aVariantName )
1308{
1309 // Hide and un-group everything by default
1310 for( size_t i = 0; i < m_cols.size(); i++ )
1311 {
1312 SetShowColumn( i, false );
1313 SetGroupColumn( i, false );
1314 }
1315
1316 std::set<wxString> seen;
1317 std::vector<wxString> order;
1318
1319 // Set columns that are present and shown
1320 for( const BOM_FIELD& field : aPreset.fieldsOrdered )
1321 {
1322 // Ignore empty fields
1323 if( !field.name || seen.count( field.name ) )
1324 continue;
1325
1326 seen.insert( field.name );
1327 order.emplace_back( field.name );
1328
1329 int col = GetFieldNameCol( field.name );
1330
1331 // Add any missing fields, if the user doesn't add any data
1332 // they won't be saved to the symbols anyway
1333 if( col == -1 )
1334 {
1335 AddColumn( field.name, field.label, true, aVariantName );
1336 col = GetFieldNameCol( field.name );
1337 }
1338 else
1339 {
1340 SetColLabelValue( col, field.label );
1341 }
1342
1343 SetGroupColumn( col, field.groupBy );
1344 SetShowColumn( col, field.show );
1345 }
1346
1347 // Set grouping columns
1349
1350 SetFieldsOrder( order );
1351
1352 // Set our sorting
1353 int sortCol = GetFieldNameCol( aPreset.sortField );
1354
1355 if( sortCol == -1 )
1357
1358 SetSorting( sortCol, aPreset.sortAsc );
1359
1360 SetFilter( aPreset.filterString );
1361 SetExcludeDNP( aPreset.excludeDNP );
1363 SetCurrentVariant( aVariantName );
1364
1365 RebuildRows();
1366}
1367
1368
1370{
1371 BOM_PRESET current;
1372 current.readOnly = false;
1373 current.fieldsOrdered = GetFieldsOrdered();
1374
1375 if( GetSortCol() >= 0 && GetSortCol() < GetNumberCols() )
1376 current.sortField = GetColFieldName( GetSortCol() );
1377
1378 current.sortAsc = GetSortAsc();
1379 current.filterString = GetFilter();
1380 current.groupSymbols = GetGroupingEnabled();
1381 current.excludeDNP = GetExcludeDNP();
1383
1384 return current;
1385}
1386
1387
1389{
1390 wxString out;
1391
1392 if( m_cols.empty() )
1393 return out;
1394
1395 int last_col = -1;
1396
1397 // Find the location for the line terminator
1398 for( size_t col = 0; col < m_cols.size(); col++ )
1399 {
1400 if( m_cols[col].m_show )
1401 last_col = static_cast<int>( col );
1402 }
1403
1404 // No shown columns
1405 if( last_col == -1 )
1406 return out;
1407
1408 auto formatField =
1409 [&]( wxString field, bool last ) -> wxString
1410 {
1411 if( !settings.keepLineBreaks )
1412 {
1413 field.Replace( wxS( "\r" ), wxS( "" ) );
1414 field.Replace( wxS( "\n" ), wxS( "" ) );
1415 }
1416
1417 if( !settings.keepTabs )
1418 {
1419 field.Replace( wxS( "\t" ), wxS( "" ) );
1420 }
1421
1422 if( !settings.stringDelimiter.IsEmpty() )
1423 {
1424 field.Replace( settings.stringDelimiter,
1425 settings.stringDelimiter + settings.stringDelimiter );
1426 }
1427
1428 return settings.stringDelimiter + field + settings.stringDelimiter
1429 + ( last ? wxString( wxS( "\n" ) ) : settings.fieldDelimiter );
1430 };
1431
1432 // Column names
1433 for( size_t col = 0; col < m_cols.size(); col++ )
1434 {
1435 if( !m_cols[col].m_show )
1436 continue;
1437
1438 out.Append( formatField( m_cols[col].m_label, col == static_cast<size_t>( last_col ) ) );
1439 }
1440
1441 // Data rows
1442 for( size_t row = 0; row < m_rows.size(); row++ )
1443 {
1444 // Don't output child rows
1445 if( GetRowFlags( static_cast<int>( row ) ) == CHILD_ITEM )
1446 continue;
1447
1448 for( size_t col = 0; col < m_cols.size(); col++ )
1449 {
1450 if( !m_cols[col].m_show )
1451 continue;
1452
1453 // Get the unannotated version of the field, e.g. no "> " or "v " by
1454 out.Append( formatField( GetExportValue( static_cast<int>( row ), static_cast<int>( col ),
1455 settings.refDelimiter, settings.refRangeDelimiter ),
1456 col == static_cast<size_t>( last_col ) ) );
1457 }
1458 }
1459
1460 return out;
1461}
1462
1463
1465{
1466 bool refListChanged = false;
1467
1468 for( const SCH_REFERENCE& ref : aRefs )
1469 {
1470 if( !m_symbolsList.Contains( ref ) )
1471 {
1472 SCH_SYMBOL* symbol = ref.GetSymbol();
1473
1474 m_symbolsList.AddItem( ref );
1475
1476 KIID_PATH key = makeDataStoreKey( ref.GetSheetPath(), *symbol );
1477
1478 // Update the fields of every reference
1479 for( const SCH_FIELD& field : symbol->GetFields() )
1480 {
1481 if( !field.IsPrivate() )
1482 {
1483 wxString name = field.GetCanonicalName();
1484 wxString value = symbol->Schematic()->ConvertKIIDsToRefs( field.GetText() );
1485
1486 m_dataStore[key][name] = value;
1487 }
1488 }
1489
1490 refListChanged = true;
1491 }
1492 }
1493
1494 if( refListChanged )
1495 m_symbolsList.SortBySymbolPtr();
1496}
1497
1498
1500{
1501 // The schematic event listener passes us the symbol after it has been removed,
1502 // so we can't just work with a SCH_REFERENCE_LIST like the other handlers as the
1503 // references are already gone. Instead we need to prune our list.
1504
1505 // Since we now use full KIID_PATH as keys, we need to find and remove all entries
1506 // that correspond to this symbol (their keys end with the symbol's UUID)
1507 KIID symbolUuid = aSymbol.m_Uuid;
1508 std::vector<KIID_PATH> keysToRemove;
1509
1510 for( const auto& [key, value] : m_dataStore )
1511 {
1512 if( !key.empty() && ( key.back() == symbolUuid ) )
1513 keysToRemove.push_back( key );
1514 }
1515
1516 for( const KIID_PATH& key : keysToRemove )
1517 m_dataStore.erase( key );
1518
1519 // Remove all refs that match this symbol using remove_if
1520 m_symbolsList.erase( std::remove_if( m_symbolsList.begin(), m_symbolsList.end(),
1521 [&aSymbol]( const SCH_REFERENCE& ref ) -> bool
1522 {
1523 return ref.GetSymbol()->m_Uuid == aSymbol.m_Uuid;
1524 } ),
1525 m_symbolsList.end() );
1526}
1527
1528
1530{
1531 for( const SCH_REFERENCE& ref : aRefs )
1532 {
1533 int index = m_symbolsList.FindRefByFullPath( ref.GetFullPath() );
1534
1535 if( index != -1 )
1536 {
1537 KIID_PATH key = makeDataStoreKey( ref.GetSheetPath(), *ref.GetSymbol() );
1538 m_dataStore.erase( key );
1539 m_symbolsList.RemoveItem( index );
1540 }
1541 }
1542}
1543
1544
1546 const wxString& aVariantName )
1547{
1548 bool refListChanged = false;
1549
1550 for( const SCH_REFERENCE& ref : aRefs )
1551 {
1552 // Update the fields of every reference. Do this by iterating through the data model
1553 // columns; we must have all fields in the symbol added to the data model at this point,
1554 // and some of the data model columns may be variables that are not present in the symbol
1555 for( const DATA_MODEL_COL& col : m_cols )
1556 updateDataStoreSymbolField( ref, col.m_fieldName, aVariantName );
1557
1558 if( SCH_REFERENCE* listRef = m_symbolsList.FindItem( ref ) )
1559 {
1560 *listRef = ref;
1561 }
1562 else
1563 {
1564 m_symbolsList.AddItem( ref );
1565 refListChanged = true;
1566 }
1567 }
1568
1569 if( refListChanged )
1570 m_symbolsList.SortBySymbolPtr();
1571}
1572
1573
1574bool FIELDS_EDITOR_GRID_DATA_MODEL::DeleteRows( size_t aPosition, size_t aNumRows )
1575{
1576 size_t curNumRows = m_rows.size();
1577
1578 if( aPosition >= curNumRows )
1579 {
1580 wxFAIL_MSG( wxString::Format( wxT( "Called FIELDS_EDITOR_GRID_DATA_MODEL::DeleteRows(aPosition=%lu, "
1581 "aNumRows=%lu)\nPosition value is invalid for present table with %lu rows" ),
1582 (unsigned long) aPosition, (unsigned long) aNumRows,
1583 (unsigned long) curNumRows ) );
1584
1585 return false;
1586 }
1587
1588 if( aNumRows > curNumRows - aPosition )
1589 {
1590 aNumRows = curNumRows - aPosition;
1591 }
1592
1593 if( aNumRows >= curNumRows )
1594 {
1595 m_rows.clear();
1596 m_dataStore.clear();
1597 }
1598 else
1599 {
1600 const auto first = m_rows.begin() + aPosition;
1601 std::vector<SCH_REFERENCE> dataMapRefs = first->m_Refs;
1602 m_rows.erase( first, first + aNumRows );
1603
1604 for( const SCH_REFERENCE& ref : dataMapRefs )
1605 m_dataStore.erase( ref.GetSheetPath().Path() );
1606 }
1607
1608 if( GetView() )
1609 {
1610 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aPosition, aNumRows );
1611 GetView()->ProcessTableMessage( msg );
1612 }
1613
1614 return true;
1615}
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:522
const EDA_ANGLE & GetTextAngle() const
Definition eda_text.h:147
void SetTextPos(const VECTOR2I &aPoint)
Definition eda_text.cpp:585
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:395
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition eda_text.cpp:308
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:118
FIELD_T GetId() const
Definition sch_field.h:122
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:247
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:854
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:46
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:61
bool IsGeneratedField(const wxString &aSource)
Returns true if the string is generated, e.g contains a single text var reference.
Definition common.cpp:334
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