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