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 (C) 2023 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 <common.h>
24#include <widgets/wx_grid.h>
25#include <sch_reference_list.h>
26#include <schematic_settings.h>
27#include "string_utils.h"
28
29#include "fields_data_model.h"
30
31
32const wxString FIELDS_EDITOR_GRID_DATA_MODEL::QUANTITY_VARIABLE = wxS( "${QUANTITY}" );
33const wxString FIELDS_EDITOR_GRID_DATA_MODEL::ITEM_NUMBER_VARIABLE = wxS( "${ITEM_NUMBER}" );
34
35
36void FIELDS_EDITOR_GRID_DATA_MODEL::AddColumn( const wxString& aFieldName, const wxString& aLabel,
37 bool aAddedByUser )
38{
39 // Don't add a field twice
40 if( GetFieldNameCol( aFieldName ) != -1 )
41 return;
42
43 m_cols.push_back( { aFieldName, aLabel, aAddedByUser, false, false } );
44
45 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
46 {
47 if( SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol() )
48 updateDataStoreSymbolField( *symbol, aFieldName );
49 }
50}
51
52
54 const wxString& aFieldName )
55{
56 if( isAttribute( aFieldName ) )
57 m_dataStore[aSymbol.m_Uuid][aFieldName] = getAttributeValue( aSymbol, aFieldName );
58 else if( const SCH_FIELD* field = aSymbol.GetFieldByName( aFieldName ) )
59 m_dataStore[aSymbol.m_Uuid][aFieldName] = field->GetText();
60 // Handle fields with variables as names that are not present in the symbol
61 // by giving them the correct value
62 else if( IsTextVar( aFieldName ) )
63 m_dataStore[aSymbol.m_Uuid][aFieldName] = aFieldName;
64 else
65 m_dataStore[aSymbol.m_Uuid][aFieldName] = wxEmptyString;
66}
67
68
70{
71 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
72 {
73 if( SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol() )
74 m_dataStore[symbol->m_Uuid].erase( m_cols[aCol].m_fieldName );
75 }
76
77 m_cols.erase( m_cols.begin() + aCol );
78}
79
80
81void FIELDS_EDITOR_GRID_DATA_MODEL::RenameColumn( int aCol, const wxString& newName )
82{
83 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
84 {
85 SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol();
86
87 auto node = m_dataStore[symbol->m_Uuid].extract( m_cols[aCol].m_fieldName );
88 node.key() = newName;
89 m_dataStore[symbol->m_Uuid].insert( std::move( node ) );
90 }
91
92 m_cols[aCol].m_fieldName = newName;
93 m_cols[aCol].m_label = newName;
94}
95
96
98{
99 for( size_t i = 0; i < m_cols.size(); i++ )
100 {
101 if( m_cols[i].m_fieldName == aFieldName )
102 return (int) i;
103 }
104
105 return -1;
106}
107
108
110{
111 std::vector<BOM_FIELD> fields;
112
113 for( const DATA_MODEL_COL& col : m_cols )
114 fields.push_back( { col.m_fieldName, col.m_label, col.m_show, col.m_group } );
115
116 return fields;
117}
118
119
120void FIELDS_EDITOR_GRID_DATA_MODEL::SetFieldsOrder( const std::vector<wxString>& aNewOrder )
121{
122 size_t foundCount = 0;
123
124 for( const wxString& newField : aNewOrder )
125 {
126 for( size_t i = 0; i < m_cols.size(); i++ )
127 {
128 if( m_cols[i].m_fieldName == newField )
129 {
130 std::swap( m_cols[foundCount], m_cols[i] );
131 foundCount++;
132 }
133 }
134 }
135}
136
137
138wxString FIELDS_EDITOR_GRID_DATA_MODEL::GetValue( int aRow, int aCol )
139{
140 if( ColIsReference( aCol ) )
141 {
142 // Poor-man's tree controls
143 if( m_rows[aRow].m_Flag == GROUP_COLLAPSED )
144 return wxT( "> " ) + GetValue( m_rows[aRow], aCol );
145 else if( m_rows[aRow].m_Flag == GROUP_EXPANDED )
146 return wxT( "v " ) + GetValue( m_rows[aRow], aCol );
147 else if( m_rows[aRow].m_Flag == CHILD_ITEM )
148 return wxT( " " ) + GetValue( m_rows[aRow], aCol );
149 else
150 return wxT( " " ) + GetValue( m_rows[aRow], aCol );
151 }
152 else
153 {
154 return GetValue( m_rows[aRow], aCol );
155 }
156}
157
158
160 const wxString& refDelimiter,
161 const wxString& refRangeDelimiter,
162 bool resolveVars )
163{
164 std::vector<SCH_REFERENCE> references;
165 wxString fieldValue;
166
167 for( const SCH_REFERENCE& ref : group.m_Refs )
168 {
169 if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
170 {
171 references.push_back( ref );
172 }
173 else // Other columns are either a single value or ROW_MULTI_ITEMS
174 {
175 const KIID& symbolID = ref.GetSymbol()->m_Uuid;
176
177 if( !m_dataStore.count( symbolID )
178 || !m_dataStore[symbolID].count( m_cols[aCol].m_fieldName ) )
179 {
180 return INDETERMINATE_STATE;
181 }
182
183 wxString refFieldValue;
184
185 // Only resolve vars on actual variables, otherwise we want to get
186 // our values out of the datastore so we can show/export un-applied values
187 if( resolveVars
188 && ( IsTextVar( m_cols[aCol].m_fieldName )
189 || IsTextVar( m_dataStore[symbolID][m_cols[aCol].m_fieldName] ) ) )
190 {
191 refFieldValue = getFieldShownText( ref, m_cols[aCol].m_fieldName );
192 }
193 else
194 refFieldValue = m_dataStore[symbolID][m_cols[aCol].m_fieldName];
195
196 if( &ref == &group.m_Refs.front() )
197 fieldValue = refFieldValue;
198 else if( fieldValue != refFieldValue )
199 return INDETERMINATE_STATE;
200 }
201 }
202
203 if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
204 {
205 // Remove duplicates (other units of multi-unit parts)
206 std::sort( references.begin(), references.end(),
207 []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
208 {
209 wxString l_ref( l.GetRef() << l.GetRefNumber() );
210 wxString r_ref( r.GetRef() << r.GetRefNumber() );
211 return StrNumCmp( l_ref, r_ref, true ) < 0;
212 } );
213
214 auto logicalEnd = std::unique( references.begin(), references.end(),
215 []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
216 {
217 // If unannotated then we can't tell what units belong together
218 // so we have to leave them all
219 if( l.GetRefNumber() == wxT( "?" ) )
220 return false;
221
222 wxString l_ref( l.GetRef() << l.GetRefNumber() );
223 wxString r_ref( r.GetRef() << r.GetRefNumber() );
224 return l_ref == r_ref;
225 } );
226
227 references.erase( logicalEnd, references.end() );
228 }
229
230 if( ColIsReference( aCol ) )
231 fieldValue = SCH_REFERENCE_LIST::Shorthand( references, refDelimiter, refRangeDelimiter );
232 else if( ColIsQuantity( aCol ) )
233 fieldValue = wxString::Format( wxT( "%d" ), (int) references.size() );
234 else if( ColIsItemNumber( aCol ) && group.m_Flag != CHILD_ITEM )
235 fieldValue = wxString::Format( wxT( "%d" ), group.m_ItemNumber );
236
237 return fieldValue;
238}
239
240
241void FIELDS_EDITOR_GRID_DATA_MODEL::SetValue( int aRow, int aCol, const wxString& aValue )
242{
243 wxCHECK_RET( aCol >= 0 && aCol < (int) m_cols.size(), wxS( "Invalid column number" ) );
244
245 // Can't modify references or text variables column, e.g. ${QUANTITY}
246 if( ColIsReference( aCol )
247 || ( IsTextVar( m_cols[aCol].m_fieldName ) && !ColIsAttribute( aCol ) ) )
248 {
249 return;
250 }
251
252 DATA_MODEL_ROW& rowGroup = m_rows[aRow];
253
254 for( const SCH_REFERENCE& ref : rowGroup.m_Refs )
255 m_dataStore[ref.GetSymbol()->m_Uuid][m_cols[aCol].m_fieldName] = aValue;
256
257 m_edited = true;
258}
259
260
262{
263 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), false );
264 return m_cols[aCol].m_fieldName == GetCanonicalFieldName( REFERENCE_FIELD );
265}
266
267
269{
270 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), false );
271 return m_cols[aCol].m_fieldName == GetCanonicalFieldName( VALUE_FIELD );
272}
273
274
276{
277 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), false );
278 return m_cols[aCol].m_fieldName == QUANTITY_VARIABLE;
279}
280
281
283{
284 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), false );
285 return m_cols[aCol].m_fieldName == ITEM_NUMBER_VARIABLE;
286}
287
288
290{
291 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), false );
292 return isAttribute( m_cols[aCol].m_fieldName );
293}
294
295
297 const DATA_MODEL_ROW& rhGroup,
298 FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol,
299 bool ascending )
300{
301 // Empty rows always go to the bottom, whether ascending or descending
302 if( lhGroup.m_Refs.size() == 0 )
303 return true;
304 else if( rhGroup.m_Refs.size() == 0 )
305 return false;
306
307 // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
308 // to get the opposite sort. i.e. ~(a<b) != (a>b)
309 auto local_cmp =
310 [ ascending ]( const auto a, const auto b )
311 {
312 if( ascending )
313 return a < b;
314 else
315 return a > b;
316 };
317
318 // Primary sort key is sortCol; secondary is always REFERENCE (column 0)
319
320 wxString lhs = dataModel->GetValue( lhGroup, sortCol ).Trim( true ).Trim( false );
321 wxString rhs = dataModel->GetValue( rhGroup, sortCol ).Trim( true ).Trim( false );
322
323 if( lhs == rhs || sortCol == REFERENCE_FIELD )
324 {
325 wxString lhRef = lhGroup.m_Refs[0].GetRef() + lhGroup.m_Refs[0].GetRefNumber();
326 wxString rhRef = rhGroup.m_Refs[0].GetRef() + rhGroup.m_Refs[0].GetRefNumber();
327 return local_cmp( StrNumCmp( lhRef, rhRef, true ), 0 );
328 }
329 else
330 {
331 return local_cmp( ValueStringCompare( lhs, rhs ), 0 );
332 }
333}
334
335
337{
339
340 // We're going to sort the rows based on their first reference, so the first reference
341 // had better be the lowest one.
342 for( DATA_MODEL_ROW& row : m_rows )
343 {
344 std::sort( row.m_Refs.begin(), row.m_Refs.end(),
345 []( const SCH_REFERENCE& lhs, const SCH_REFERENCE& rhs )
346 {
347 wxString lhs_ref( lhs.GetRef() << lhs.GetRefNumber() );
348 wxString rhs_ref( rhs.GetRef() << rhs.GetRefNumber() );
349 return StrNumCmp( lhs_ref, rhs_ref, true ) < 0;
350 } );
351 }
352
353 std::sort( m_rows.begin(), m_rows.end(),
354 [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
355 {
356 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
357 } );
358
359 // Time to renumber the item numbers
360 int itemNumber = 1;
361 for( DATA_MODEL_ROW& row : m_rows )
362 {
363 row.m_ItemNumber = itemNumber++;
364 }
365
367}
368
369
371 const SCH_REFERENCE& rhRef )
372{
373 // If items are unannotated then we can't tell if they're units of the same symbol or not
374 if( lhRef.GetRefNumber() == wxT( "?" ) )
375 return false;
376
377 return ( lhRef.GetRef() == rhRef.GetRef() && lhRef.GetRefNumber() == rhRef.GetRefNumber() );
378}
379
380
382 const SCH_REFERENCE& rhRef )
383
384{
386 bool matchFound = false;
387
388 if( refCol == -1 )
389 return false;
390
391 // First check the reference column. This can be done directly out of the
392 // SCH_REFERENCEs as the references can't be edited in the grid.
393 if( m_cols[refCol].m_group )
394 {
395 // if we're grouping by reference, then only the prefix must match
396 if( lhRef.GetRef() != rhRef.GetRef() )
397 return false;
398
399 matchFound = true;
400 }
401
402 const KIID& lhRefID = lhRef.GetSymbol()->m_Uuid;
403 const KIID& rhRefID = rhRef.GetSymbol()->m_Uuid;
404
405 // Now check all the other columns.
406 for( size_t i = 0; i < m_cols.size(); ++i )
407 {
408 //Handled already
409 if( (int) i == refCol )
410 continue;
411
412 if( !m_cols[i].m_group )
413 continue;
414
415 // If the field is a variable, we need to resolve it through the symbol
416 // to get the actual current value, otherwise we need to pull it out of the
417 // store so the refresh can regroup based on values that haven't been applied
418 // to the schematic yet.
419 wxString lh, rh;
420
421 if( IsTextVar( m_cols[i].m_fieldName )
422 || IsTextVar( m_dataStore[lhRefID][m_cols[i].m_fieldName] ) )
423 {
424 lh = getFieldShownText( lhRef, m_cols[i].m_fieldName );
425 }
426 else
427 lh = m_dataStore[lhRefID][m_cols[i].m_fieldName];
428
429 if( IsTextVar( m_cols[i].m_fieldName )
430 || IsTextVar( m_dataStore[rhRefID][m_cols[i].m_fieldName] ) )
431 {
432 rh = getFieldShownText( rhRef, m_cols[i].m_fieldName );
433 }
434 else
435 rh = m_dataStore[rhRefID][m_cols[i].m_fieldName];
436
437 wxString fieldName = m_cols[i].m_fieldName;
438
439 if( lh != rh )
440 return false;
441
442 matchFound = true;
443 }
444
445 return matchFound;
446}
447
448
450 const wxString& aFieldName )
451{
452 SCH_FIELD* field = aRef.GetSymbol()->GetFieldByName( aFieldName );
453
454 if( field )
455 return field->GetShownText( &aRef.GetSheetPath(), false );
456
457 // Handle fields with variables as names that are not present in the symbol
458 // by giving them the correct value by resolving against the symbol
459 if( IsTextVar( aFieldName ) )
460 {
461 int depth = 0;
462 const SCH_SHEET_PATH& path = aRef.GetSheetPath();
463
464 std::function<bool( wxString* )> symbolResolver =
465 [&]( wxString* token ) -> bool
466 {
467 return aRef.GetSymbol()->ResolveTextVar( &path, token, depth + 1 );
468 };
469
470 return ExpandTextVars( aFieldName, &symbolResolver );
471 }
472
473 return wxEmptyString;
474}
475
476
477bool FIELDS_EDITOR_GRID_DATA_MODEL::isAttribute( const wxString& aFieldName )
478{
479 return aFieldName == wxS( "${DNP}" )
480 || aFieldName == wxS( "${EXCLUDE_FROM_BOARD}" )
481 || aFieldName == wxS( "${EXCLUDE_FROM_BOM}" )
482 || aFieldName == wxS( "${EXCLUDE_FROM_SIM}" );
483}
484
485
487 const wxString& aAttributeName )
488{
489 if( aAttributeName == wxS( "${DNP}" ) )
490 return aSymbol.GetDNP() ? wxS( "1" ) : wxS( "0" );
491
492 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
493 return aSymbol.GetExcludedFromBoard() ? wxS( "1" ) : wxS( "0" );
494
495 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
496 return aSymbol.GetExcludedFromBOM() ? wxS( "1" ) : wxS( "0" );
497
498 if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
499 return aSymbol.GetExcludedFromSim() ? wxS( "1" ) : wxS( "0" );
500
501 return wxS( "0" );
502}
503
505 const wxString& aAttributeName,
506 const wxString& aValue )
507{
508 if( aAttributeName == wxS( "${DNP}" ) )
509 aSymbol.SetDNP( aValue == wxS( "1" ) );
510 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
511 aSymbol.SetExcludedFromBoard( aValue == wxS( "1" ) );
512 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
513 aSymbol.SetExcludedFromBOM( aValue == wxS( "1" ) );
514 else if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
515 aSymbol.SetExcludedFromSim( aValue == wxS( "1" ) );
516}
517
518
520{
521 m_rebuildsEnabled = true;
522}
523
524
526{
527 m_rebuildsEnabled = false;
528}
529
530
532{
533 if( !m_rebuildsEnabled )
534 return;
535
536 if( GetView() )
537 {
538 // Commit any pending in-place edits before the row gets moved out from under
539 // the editor.
540 static_cast<WX_GRID*>( GetView() )->CommitPendingChanges( true );
541
542 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
543 GetView()->ProcessTableMessage( msg );
544 }
545
546 m_rows.clear();
547
548 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
549 {
551
552 if( !m_filter.IsEmpty() && !WildCompareString( m_filter, ref.GetFullRef(), false ) )
553 continue;
554
555 if( m_excludeDNP && ref.GetSymbol()->GetDNP() )
556 continue;
557
559 continue;
560
561 // Check if the symbol if on the current sheet or, in the sheet path somewhere
562 // depending on scope
563 if( ( m_scope == SCOPE::SCOPE_SHEET && ref.GetSheetPath() != m_path )
564 || ( m_scope == SCOPE::SCOPE_SHEET_RECURSIVE
565 && !ref.GetSheetPath().IsContainedWithin( m_path ) ) )
566 {
567 continue;
568 }
569
570 bool matchFound = false;
571
572 // Performance optimization for ungrouped case to skip the N^2 for loop
573 if( !m_groupingEnabled && !ref.IsMultiUnit() )
574 {
575 m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
576 continue;
577 }
578
579 // See if we already have a row which this symbol fits into
580 for( DATA_MODEL_ROW& row : m_rows )
581 {
582 // all group members must have identical refs so just use the first one
583 SCH_REFERENCE rowRef = row.m_Refs[0];
584
585 if( unitMatch( ref, rowRef ) )
586 {
587 matchFound = true;
588 row.m_Refs.push_back( ref );
589 break;
590 }
591 else if( m_groupingEnabled && groupMatch( ref, rowRef ) )
592 {
593 matchFound = true;
594 row.m_Refs.push_back( ref );
595 row.m_Flag = GROUP_COLLAPSED;
596 break;
597 }
598 }
599
600 if( !matchFound )
601 m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
602 }
603
604 if( GetView() )
605 {
606 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() );
607 GetView()->ProcessTableMessage( msg );
608 }
609
610 Sort();
611}
612
613
615{
616 std::vector<DATA_MODEL_ROW> children;
617
618 for( SCH_REFERENCE& ref : m_rows[aRow].m_Refs )
619 {
620 bool matchFound = false;
621
622 // See if we already have a child group which this symbol fits into
623 for( DATA_MODEL_ROW& child : children )
624 {
625 // group members are by definition all matching, so just check
626 // against the first member
627 if( unitMatch( ref, child.m_Refs[0] ) )
628 {
629 matchFound = true;
630 child.m_Refs.push_back( ref );
631 break;
632 }
633 }
634
635 if( !matchFound )
636 children.emplace_back( DATA_MODEL_ROW( ref, CHILD_ITEM ) );
637 }
638
639 if( children.size() < 2 )
640 return;
641
642 std::sort( children.begin(), children.end(),
643 [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
644 {
645 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
646 } );
647
648 m_rows[aRow].m_Flag = GROUP_EXPANDED;
649 m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() );
650
651 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, children.size() );
652 GetView()->ProcessTableMessage( msg );
653}
654
655
657{
658 auto firstChild = m_rows.begin() + aRow + 1;
659 auto afterLastChild = firstChild;
660 int deleted = 0;
661
662 while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM )
663 {
664 deleted++;
665 afterLastChild++;
666 }
667
668 m_rows[aRow].m_Flag = GROUP_COLLAPSED;
669 m_rows.erase( firstChild, afterLastChild );
670
671 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted );
672 GetView()->ProcessTableMessage( msg );
673}
674
675
677{
678 DATA_MODEL_ROW& group = m_rows[aRow];
679
680 if( group.m_Flag == GROUP_COLLAPSED )
681 ExpandRow( aRow );
682 else if( group.m_Flag == GROUP_EXPANDED )
683 CollapseRow( aRow );
684}
685
686
688{
689 for( size_t i = 0; i < m_rows.size(); ++i )
690 {
691 if( m_rows[i].m_Flag == GROUP_EXPANDED )
692 {
693 CollapseRow( i );
695 }
696 }
697}
698
699
701{
702 for( size_t i = 0; i < m_rows.size(); ++i )
703 {
704 if( m_rows[i].m_Flag == GROUP_COLLAPSED_DURING_SORT )
705 ExpandRow( i );
706 }
707}
708
709
711 std::function<void( SCH_SYMBOL&, SCH_SHEET_PATH& )> symbolChangeHandler )
712{
713 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
714 {
715 SCH_SYMBOL& symbol = *m_symbolsList[i].GetSymbol();
716
717 symbolChangeHandler( symbol, m_symbolsList[i].GetSheetPath() );
718
719 const std::map<wxString, wxString>& fieldStore = m_dataStore[symbol.m_Uuid];
720
721 for( const std::pair<wxString, wxString> srcData : fieldStore )
722 {
723 const wxString& srcName = srcData.first;
724 const wxString& srcValue = srcData.second;
725
726 // Attributes bypass the field logic, so handle them first
727 if( isAttribute( srcName ) )
728 {
729 setAttributeValue( *m_symbolsList[i].GetSymbol(), srcName, srcValue );
730 continue;
731 }
732
733 // Skip special fields with variables as names (e.g. ${QUANTITY}),
734 // they can't be edited
735 if( IsTextVar( srcName ) )
736 continue;
737
738 SCH_FIELD* destField = symbol.FindField( srcName );
739 int col = GetFieldNameCol( srcName );
740 bool userAdded = ( col != -1 && m_cols[col].m_userAdded );
741
742 // Add a not existing field if it has a value for this symbol
743 bool createField = !destField && ( !srcValue.IsEmpty() || userAdded );
744
745 if( createField )
746 {
747 const VECTOR2I symbolPos = symbol.GetPosition();
748 destField = symbol.AddField( SCH_FIELD( symbolPos, -1, &symbol, srcName ) );
749 }
750
751 if( !destField )
752 continue;
753
754 if( destField->GetId() == REFERENCE_FIELD )
755 {
756 // Reference is not editable from this dialog
757 }
758 else if( destField->GetId() == VALUE_FIELD )
759 {
760 // Value field cannot be empty
761 if( !srcValue.IsEmpty() )
762 symbol.SetValueFieldText( srcValue );
763 }
764 else if( destField->GetId() == FOOTPRINT_FIELD )
765 {
766 symbol.SetFootprintFieldText( srcValue );
767 }
768 else
769 {
770 destField->SetText( srcValue );
771 }
772 }
773
774 for( int ii = symbol.GetFields().size() - 1; ii >= MANDATORY_FIELDS; ii-- )
775 {
776 if( fieldStore.count( symbol.GetFields()[ii].GetName() ) == 0 )
777 symbol.GetFields().erase( symbol.GetFields().begin() + ii );
778 }
779 }
780
781 m_edited = false;
782}
783
784
786{
787 int width = 0;
788
789 if( ColIsReference( aCol ) )
790 {
791 for( int row = 0; row < GetNumberRows(); ++row )
792 width = std::max( width, KIUI::GetTextSize( GetValue( row, aCol ), GetView() ).x );
793 }
794 else
795 {
796 wxString fieldName = GetColFieldName( aCol ); // symbol fieldName or Qty string
797
798 for( unsigned symbolRef = 0; symbolRef < m_symbolsList.GetCount(); ++symbolRef )
799 {
800 const KIID& symbolID = m_symbolsList[symbolRef].GetSymbol()->m_Uuid;
801 wxString text = m_dataStore[symbolID][fieldName];
802
803 width = std::max( width, KIUI::GetTextSize( text, GetView() ).x );
804 }
805 }
806
807 return width;
808}
809
810
812{
813 // Hide and un-group everything by default
814 for( size_t i = 0; i < m_cols.size(); i++ )
815 {
816 SetShowColumn( i, false );
817 SetGroupColumn( i, false );
818 }
819
820 std::vector<wxString> order;
821
822 // Set columns that are present and shown
823 for( BOM_FIELD field : aPreset.fieldsOrdered )
824 {
825 order.emplace_back( field.name );
826
827 int col = GetFieldNameCol( field.name );
828
829 // Add any missing fields, if the user doesn't add any data
830 // they won't be saved to the symbols anyway
831 if( col == -1 )
832 {
833 AddColumn( field.name, field.label, true );
834 col = GetFieldNameCol( field.name );
835 }
836 else
837 SetColLabelValue( col, field.label );
838
839 SetGroupColumn( col, field.groupBy );
840 SetShowColumn( col, field.show );
841 }
842
843 // Set grouping columns
845
846 SetFieldsOrder( order );
847
848 // Set our sorting
849 int sortCol = GetFieldNameCol( aPreset.sortField );
850
851 if( sortCol != -1 )
852 SetSorting( sortCol, aPreset.sortAsc );
853 else
855
856 SetFilter( aPreset.filterString );
857 SetExcludeDNP( aPreset.excludeDNP );
859
860 RebuildRows();
861}
862
863
865{
866 BOM_PRESET current;
867 current.readOnly = false;
869 current.sortField = GetColFieldName( GetSortCol() );
870 current.sortAsc = GetSortAsc();
871 current.filterString = GetFilter();
873 current.excludeDNP = GetExcludeDNP();
875
876 return current;
877}
878
879
881{
882 wxString out;
883
884 if( m_cols.empty() )
885 return out;
886
887 int last_col = -1;
888
889 // Find the location for the line terminator
890 for( size_t col = 0; col < m_cols.size(); col++ )
891 {
892 if( m_cols[col].m_show )
893 last_col = (int) col;
894 }
895
896 // No shown columns
897 if( last_col == -1 )
898 return out;
899
900 auto formatField = [&]( wxString field, bool last ) -> wxString
901 {
902 if( !settings.keepLineBreaks )
903 {
904 field.Replace( wxS( "\r" ), wxS( "" ) );
905 field.Replace( wxS( "\n" ), wxS( "" ) );
906 }
907
908 if( !settings.keepTabs )
909 {
910 field.Replace( wxS( "\t" ), wxS( "" ) );
911 }
912
913 if( !settings.stringDelimiter.IsEmpty() )
914 field.Replace( settings.stringDelimiter,
915 settings.stringDelimiter + settings.stringDelimiter );
916
917 return settings.stringDelimiter + field + settings.stringDelimiter
918 + ( last ? wxString( wxS( "\n" ) ) : settings.fieldDelimiter );
919 };
920
921 // Column names
922 for( size_t col = 0; col < m_cols.size(); col++ )
923 {
924 if( !m_cols[col].m_show )
925 continue;
926
927 out.Append( formatField( m_cols[col].m_label, col == (size_t) last_col ) );
928 }
929
930 // Data rows
931 for( size_t row = 0; row < m_rows.size(); row++ )
932 {
933 // Don't output child rows
934 if( GetRowFlags( (int) row ) == CHILD_ITEM )
935 continue;
936
937 for( size_t col = 0; col < m_cols.size(); col++ )
938 {
939 if( !m_cols[col].m_show )
940 continue;
941
942 // Get the unanottated version of the field, e.g. no "> " or "v " by
943 out.Append( formatField( GetExportValue( (int) row, (int) col, settings.refDelimiter,
944 settings.refRangeDelimiter ),
945 col == (size_t) last_col ) );
946 }
947 }
948
949 return out;
950}
951
952
954{
955 for( const SCH_REFERENCE& ref : aRefs )
956 {
957 if( !m_symbolsList.Contains( ref ) )
958 {
959 m_symbolsList.AddItem( ref );
960
961 // Update the fields of every reference
962 for( const SCH_FIELD& field : ref.GetSymbol()->GetFields() )
963 m_dataStore[ref.GetSymbol()->m_Uuid][field.GetCanonicalName()] = field.GetText();
964 }
965 }
966}
967
968
970{
971 // The schematic event listener passes us the symbol after it has been removed,
972 // so we can't just work with a SCH_REFERENCE_LIST like the other handlers as the
973 // references are already gone. Instead we need to prune our list.
974 m_dataStore[aSymbol.m_Uuid].clear();
975
976 // Remove all refs that match this symbol using remove_if
978 [&aSymbol]( const SCH_REFERENCE& ref ) -> bool
979 {
980 return ref.GetSymbol()->m_Uuid == aSymbol.m_Uuid;
981 } ),
982 m_symbolsList.end() );
983}
984
985
987{
988 for( const SCH_REFERENCE& ref : aRefs )
989 {
990 int index = m_symbolsList.FindRefByFullPath( ref.GetFullPath() );
991
992 if( index != -1 )
993 {
994 m_symbolsList.RemoveItem( index );
995
996 // If we're out of instances then remove the symbol, too
997 if( ref.GetSymbol()->GetInstances().empty() )
998 m_dataStore.erase( ref.GetSymbol()->m_Uuid );
999 }
1000 }
1001}
1002
1003
1005{
1006 for( const SCH_REFERENCE& ref : aRefs )
1007 {
1008 // Update the fields of every reference. Do this by iterating through the data model
1009 // columns; we must have all fields in the symbol added to the data model at this point,
1010 // and some of the data model columns may be variables that are not present in the symbol
1011 for( const DATA_MODEL_COL& col : m_cols )
1012 updateDataStoreSymbolField( *ref.GetSymbol(), col.m_fieldName );
1013
1014 if( !m_symbolsList.Contains( ref ) )
1015 m_symbolsList.AddItem( ref );
1016 }
1017}
const KIID m_Uuid
Definition: eda_item.h:485
wxString GetColFieldName(int aCol)
std::vector< DATA_MODEL_ROW > m_rows
int GetFieldNameCol(wxString aFieldName)
void ApplyBomPreset(const BOM_PRESET &preset)
void SetFieldsOrder(const std::vector< wxString > &aNewOrder)
wxString getAttributeValue(const SCH_SYMBOL &, const wxString &aAttributeName)
void updateDataStoreSymbolField(const SCH_SYMBOL &aSymbol, const wxString &aFieldName)
bool groupMatch(const SCH_REFERENCE &lhRef, const SCH_REFERENCE &rhRef)
bool unitMatch(const SCH_REFERENCE &lhRef, const SCH_REFERENCE &rhRef)
wxString GetExportValue(int aRow, int aCol, const wxString &refDelimiter, const wxString &refRangeDelimiter)
wxString getFieldShownText(const SCH_REFERENCE &aRef, const wxString &aFieldName)
void RenameColumn(int aCol, const wxString &newName)
wxString Export(const BOM_FMT_PRESET &settings)
void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser)
std::vector< DATA_MODEL_COL > m_cols
void SetSorting(int aCol, bool ascending)
void SetFilter(const wxString &aFilter)
static bool cmp(const DATA_MODEL_ROW &lhGroup, const DATA_MODEL_ROW &rhGroup, FIELDS_EDITOR_GRID_DATA_MODEL *dataModel, int sortCol, bool ascending)
static const wxString ITEM_NUMBER_VARIABLE
void SetIncludeExcludedFromBOM(bool include)
bool isAttribute(const wxString &aFieldName)
wxString GetValue(int aRow, int aCol) override
void UpdateReferences(const SCH_REFERENCE_LIST &aRefs)
void ApplyData(std::function< void(SCH_SYMBOL &, SCH_SHEET_PATH &)> symbolChangeHandler)
GROUP_TYPE GetRowFlags(int aRow)
std::map< KIID, std::map< wxString, wxString > > m_dataStore
static const wxString QUANTITY_VARIABLE
void SetGroupColumn(int aCol, bool group)
void RemoveSymbol(const SCH_SYMBOL &aSymbol)
void SetValue(int aRow, int aCol, const wxString &aValue) override
void SetColLabelValue(int aCol, const wxString &aLabel) override
void RemoveReferences(const SCH_REFERENCE_LIST &aRefs)
void SetShowColumn(int aCol, bool show)
void setAttributeValue(SCH_SYMBOL &aSymbol, const wxString &aAttributeName, const wxString &aValue)
void AddReferences(const SCH_REFERENCE_LIST &aRefs)
const std::vector< BOM_FIELD > GetFieldsOrdered()
Definition: kiid.h:49
Instances are attached to a symbol or sheet and provide a place for the symbol's value,...
Definition: sch_field.h:51
int GetId() const
Definition: sch_field.h:133
wxString GetShownText(const SCH_SHEET_PATH *aPath, bool aAllowExtraText, int aDepth=0) const
Definition: sch_field.cpp:197
void SetText(const wxString &aText) override
Definition: sch_field.cpp:1150
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
bool Contains(const SCH_REFERENCE &aItem) const
Return true if aItem exists in this list.
iterator erase(iterator position)
size_t GetCount() const
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.
int FindRefByFullPath(const wxString &aFullPath) const
Search the list for a symbol with the given KIID path (as string).
void AddItem(const SCH_REFERENCE &aItem)
void RemoveItem(unsigned int aIndex)
Remove an item from the list of references.
A helper to define a symbol's reference designator in a schematic.
const SCH_SHEET_PATH & GetSheetPath() const
wxString GetFullRef() const
SCH_SYMBOL * GetSymbol() const
wxString GetRef() const
bool IsMultiUnit() const
wxString GetRefNumber() const
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
bool IsContainedWithin(const SCH_SHEET_PATH &aSheetPathToTest) const
Check if this path is contained inside aSheetPathToTest.
Schematic symbol object.
Definition: sch_symbol.h:105
SCH_FIELD * GetFieldByName(const wxString &aFieldName)
Return a field in this symbol.
Definition: sch_symbol.cpp:874
void SetValueFieldText(const wxString &aValue)
Definition: sch_symbol.cpp:828
SCH_FIELD * FindField(const wxString &aFieldName, bool aIncludeDefaultFields=true, bool aCaseInsensitive=false)
Search for a SCH_FIELD with aFieldName.
Definition: sch_symbol.cpp:933
void SetFootprintFieldText(const wxString &aFootprint)
Definition: sch_symbol.cpp:844
VECTOR2I GetPosition() const override
Definition: sch_symbol.h:779
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.
Definition: sch_symbol.cpp:913
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly)
Populate a std::vector with SCH_FIELDs.
Definition: sch_symbol.cpp:898
void SetDNP(bool aDNP)
Definition: symbol.h:154
bool GetExcludedFromBoard() const
Definition: symbol.h:148
bool GetExcludedFromBOM() const
Definition: symbol.h:142
void SetExcludedFromSim(bool aExcludeFromSim) override
Set or clear the exclude from simulation flag.
Definition: symbol.h:135
bool GetDNP() const
Set or clear the 'Do Not Populate' flaga.
Definition: symbol.h:153
void SetExcludedFromBOM(bool aExcludeFromBOM)
Set or clear the exclude from schematic bill of materials flag.
Definition: symbol.h:141
void SetExcludedFromBoard(bool aExcludeFromBoard)
Set or clear exclude from board netlist flag.
Definition: symbol.h:147
bool GetExcludedFromSim() const override
Definition: symbol.h:136
bool IsTextVar(const wxString &aSource)
Returns true if the string is a text var, e.g starts with ${.
Definition: common.cpp:127
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject)
Definition: common.cpp:58
The common library.
@ GROUP_COLLAPSED_DURING_SORT
@ GROUP_EXPANDED
@ GROUP_COLLAPSED
@ GROUP_SINGLETON
@ CHILD_ITEM
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:77
int StrNumCmp(const wxString &aString1, const wxString &aString2, bool aIgnoreCase)
Compare two strings with alphanumerical content.
bool WildCompareString(const wxString &pattern, const wxString &string_to_tst, bool case_sensitive)
Compare a string against wild card (* and ?) pattern using the usual rules.
int ValueStringCompare(const wxString &strFWord, const wxString &strSWord)
Compare strings like the strcmp function but handle numbers and modifiers within the string text corr...
wxString label
Definition: bom_settings.h:33
bool groupBy
Definition: bom_settings.h:35
wxString name
Definition: bom_settings.h:32
wxString fieldDelimiter
Definition: bom_settings.h:83
wxString stringDelimiter
Definition: bom_settings.h:84
wxString refRangeDelimiter
Definition: bom_settings.h:86
wxString refDelimiter
Definition: bom_settings.h:85
wxString sortField
Definition: bom_settings.h:54
bool groupSymbols
Definition: bom_settings.h:57
std::vector< BOM_FIELD > fieldsOrdered
Definition: bom_settings.h:53
bool includeExcludedFromBOM
Definition: bom_settings.h:59
bool readOnly
Definition: bom_settings.h:52
bool excludeDNP
Definition: bom_settings.h:58
bool sortAsc
Definition: bom_settings.h:55
wxString filterString
Definition: bom_settings.h:56
std::vector< SCH_REFERENCE > m_Refs
wxString GetCanonicalFieldName(int idx)
@ FOOTPRINT_FIELD
Field Name Module PCB, i.e. "16DIP300".
@ VALUE_FIELD
Field Value of part, i.e. "3.3K".
@ MANDATORY_FIELDS
The first 5 are mandatory, and must be instantiated in SCH_COMPONENT and LIB_PART constructors.
@ REFERENCE_FIELD
Field Reference of part, i.e. "IC21".
#define INDETERMINATE_STATE
Used for holding indeterminate values, such as with multiple selections holding different values or c...
Definition: ui_common.h:46