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( const SCH_FIELD* field = aSymbol.GetFieldByName( aFieldName ) )
57 m_dataStore[aSymbol.m_Uuid][aFieldName] = field->GetText();
58 else if( isAttribute( aFieldName ) )
59 m_dataStore[aSymbol.m_Uuid][aFieldName] = getAttributeValue( aSymbol, aFieldName );
60
61 // Handle fields with variables as names that are not present in the symbol
62 // by giving them the correct value
63 else if( IsTextVar( aFieldName ) )
64 m_dataStore[aSymbol.m_Uuid][aFieldName] = aFieldName;
65 else
66 m_dataStore[aSymbol.m_Uuid][aFieldName] = wxEmptyString;
67}
68
69
71{
72 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
73 {
74 if( SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol() )
75 m_dataStore[symbol->m_Uuid].erase( m_cols[aCol].m_fieldName );
76 }
77
78 m_cols.erase( m_cols.begin() + aCol );
79}
80
81
82void FIELDS_EDITOR_GRID_DATA_MODEL::RenameColumn( int aCol, const wxString& newName )
83{
84 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
85 {
86 SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol();
87
88 auto node = m_dataStore[symbol->m_Uuid].extract( m_cols[aCol].m_fieldName );
89 node.key() = newName;
90 m_dataStore[symbol->m_Uuid].insert( std::move( node ) );
91 }
92
93 m_cols[aCol].m_fieldName = newName;
94 m_cols[aCol].m_label = newName;
95}
96
97
99{
100 for( size_t i = 0; i < m_cols.size(); i++ )
101 {
102 if( m_cols[i].m_fieldName == aFieldName )
103 return (int) i;
104 }
105
106 return -1;
107}
108
109
111{
112 std::vector<BOM_FIELD> fields;
113
114 for( const DATA_MODEL_COL& col : m_cols )
115 fields.push_back( { col.m_fieldName, col.m_label, col.m_show, col.m_group } );
116
117 return fields;
118}
119
120
121void FIELDS_EDITOR_GRID_DATA_MODEL::SetFieldsOrder( const std::vector<wxString>& aNewOrder )
122{
123 size_t foundCount = 0;
124
125 for( const wxString& newField : aNewOrder )
126 {
127 for( size_t i = 0; i < m_cols.size(); i++ )
128 {
129 if( m_cols[i].m_fieldName == newField )
130 {
131 std::swap( m_cols[foundCount], m_cols[i] );
132 foundCount++;
133 }
134 }
135 }
136}
137
138
139wxString FIELDS_EDITOR_GRID_DATA_MODEL::GetValue( int aRow, int aCol )
140{
141 if( ColIsReference( aCol ) )
142 {
143 // Poor-man's tree controls
144 if( m_rows[aRow].m_Flag == GROUP_COLLAPSED )
145 return wxT( "> " ) + GetValue( m_rows[aRow], aCol );
146 else if( m_rows[aRow].m_Flag == GROUP_EXPANDED )
147 return wxT( "v " ) + GetValue( m_rows[aRow], aCol );
148 else if( m_rows[aRow].m_Flag == CHILD_ITEM )
149 return wxT( " " ) + GetValue( m_rows[aRow], aCol );
150 else
151 return wxT( " " ) + GetValue( m_rows[aRow], aCol );
152 }
153 else
154 {
155 return GetValue( m_rows[aRow], aCol );
156 }
157}
158
159
161 const wxString& refDelimiter,
162 const wxString& refRangeDelimiter,
163 bool resolveVars )
164{
165 std::vector<SCH_REFERENCE> references;
166 wxString fieldValue;
167
168 for( const SCH_REFERENCE& ref : group.m_Refs )
169 {
170 if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
171 {
172 references.push_back( ref );
173 }
174 else // Other columns are either a single value or ROW_MULTI_ITEMS
175 {
176 const KIID& symbolID = ref.GetSymbol()->m_Uuid;
177
178 if( !m_dataStore.count( symbolID )
179 || !m_dataStore[symbolID].count( m_cols[aCol].m_fieldName ) )
180 {
181 return INDETERMINATE_STATE;
182 }
183
184 wxString refFieldValue;
185
186 // Only resolve vars on actual variables, otherwise we want to get
187 // our values out of the datastore so we can show/export un-applied values
188 if( resolveVars
189 && ( IsTextVar( m_cols[aCol].m_fieldName )
190 || IsTextVar( m_dataStore[symbolID][m_cols[aCol].m_fieldName] ) ) )
191 {
192 refFieldValue = getFieldShownText( ref, m_cols[aCol].m_fieldName );
193 }
194 else
195 refFieldValue = m_dataStore[symbolID][m_cols[aCol].m_fieldName];
196
197 if( &ref == &group.m_Refs.front() )
198 fieldValue = refFieldValue;
199 else if( fieldValue != refFieldValue )
200 return INDETERMINATE_STATE;
201 }
202 }
203
204 if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
205 {
206 // Remove duplicates (other units of multi-unit parts)
207 std::sort( references.begin(), references.end(),
208 []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
209 {
210 wxString l_ref( l.GetRef() << l.GetRefNumber() );
211 wxString r_ref( r.GetRef() << r.GetRefNumber() );
212 return StrNumCmp( l_ref, r_ref, true ) < 0;
213 } );
214
215 auto logicalEnd = std::unique( references.begin(), references.end(),
216 []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
217 {
218 // If unannotated then we can't tell what units belong together
219 // so we have to leave them all
220 if( l.GetRefNumber() == wxT( "?" ) )
221 return false;
222
223 wxString l_ref( l.GetRef() << l.GetRefNumber() );
224 wxString r_ref( r.GetRef() << r.GetRefNumber() );
225 return l_ref == r_ref;
226 } );
227
228 references.erase( logicalEnd, references.end() );
229 }
230
231 if( ColIsReference( aCol ) )
232 fieldValue = SCH_REFERENCE_LIST::Shorthand( references, refDelimiter, refRangeDelimiter );
233 else if( ColIsQuantity( aCol ) )
234 fieldValue = wxString::Format( wxT( "%d" ), (int) references.size() );
235 else if( ColIsItemNumber( aCol ) && group.m_Flag != CHILD_ITEM )
236 fieldValue = wxString::Format( wxT( "%d" ), group.m_ItemNumber );
237
238 return fieldValue;
239}
240
241
242void FIELDS_EDITOR_GRID_DATA_MODEL::SetValue( int aRow, int aCol, const wxString& aValue )
243{
244 wxCHECK_RET( aCol >= 0 && aCol < (int) m_cols.size(), wxS( "Invalid column number" ) );
245
246 // Can't modify references or text variables column, e.g. ${QUANTITY}
247 if( ColIsReference( aCol )
248 || ( IsTextVar( m_cols[aCol].m_fieldName ) && !ColIsAttribute( aCol ) ) )
249 {
250 return;
251 }
252
253 DATA_MODEL_ROW& rowGroup = m_rows[aRow];
254
255 for( const SCH_REFERENCE& ref : rowGroup.m_Refs )
256 m_dataStore[ref.GetSymbol()->m_Uuid][m_cols[aCol].m_fieldName] = aValue;
257
258 m_edited = true;
259}
260
261
263{
264 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), false );
265 return m_cols[aCol].m_fieldName == GetCanonicalFieldName( REFERENCE_FIELD );
266}
267
268
270{
271 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), false );
272 return m_cols[aCol].m_fieldName == GetCanonicalFieldName( VALUE_FIELD );
273}
274
275
277{
278 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), false );
279 return m_cols[aCol].m_fieldName == QUANTITY_VARIABLE;
280}
281
282
284{
285 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), false );
286 return m_cols[aCol].m_fieldName == ITEM_NUMBER_VARIABLE;
287}
288
289
291{
292 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), false );
293 return isAttribute( m_cols[aCol].m_fieldName );
294}
295
296
298 const DATA_MODEL_ROW& rhGroup,
299 FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol,
300 bool ascending )
301{
302 // Empty rows always go to the bottom, whether ascending or descending
303 if( lhGroup.m_Refs.size() == 0 )
304 return true;
305 else if( rhGroup.m_Refs.size() == 0 )
306 return false;
307
308 // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
309 // to get the opposite sort. i.e. ~(a<b) != (a>b)
310 auto local_cmp =
311 [ ascending ]( const auto a, const auto b )
312 {
313 if( ascending )
314 return a < b;
315 else
316 return a > b;
317 };
318
319 // Primary sort key is sortCol; secondary is always REFERENCE (column 0)
320
321 wxString lhs = dataModel->GetValue( (DATA_MODEL_ROW&) lhGroup, sortCol );
322 wxString rhs = dataModel->GetValue( (DATA_MODEL_ROW&) rhGroup, sortCol );
323
324 if( lhs == rhs || sortCol == REFERENCE_FIELD )
325 {
326 wxString lhRef = lhGroup.m_Refs[0].GetRef() + lhGroup.m_Refs[0].GetRefNumber();
327 wxString rhRef = rhGroup.m_Refs[0].GetRef() + rhGroup.m_Refs[0].GetRefNumber();
328 return local_cmp( StrNumCmp( lhRef, rhRef, true ), 0 );
329 }
330 else
331 {
332 return local_cmp( ValueStringCompare( lhs, rhs ), 0 );
333 }
334}
335
336
338{
340
341 // We're going to sort the rows based on their first reference, so the first reference
342 // had better be the lowest one.
343 for( DATA_MODEL_ROW& row : m_rows )
344 {
345 std::sort( row.m_Refs.begin(), row.m_Refs.end(),
346 []( const SCH_REFERENCE& lhs, const SCH_REFERENCE& rhs )
347 {
348 wxString lhs_ref( lhs.GetRef() << lhs.GetRefNumber() );
349 wxString rhs_ref( rhs.GetRef() << rhs.GetRefNumber() );
350 return StrNumCmp( lhs_ref, rhs_ref, true ) < 0;
351 } );
352 }
353
354 std::sort( m_rows.begin(), m_rows.end(),
355 [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
356 {
357 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
358 } );
359
360 // Time to renumber the item numbers
361 int itemNumber = 1;
362 for( DATA_MODEL_ROW& row : m_rows )
363 {
364 row.m_ItemNumber = itemNumber++;
365 }
366
368}
369
370
372 const SCH_REFERENCE& rhRef )
373{
374 // If items are unannotated then we can't tell if they're units of the same symbol or not
375 if( lhRef.GetRefNumber() == wxT( "?" ) )
376 return false;
377
378 return ( lhRef.GetRef() == rhRef.GetRef() && lhRef.GetRefNumber() == rhRef.GetRefNumber() );
379}
380
381
383 const SCH_REFERENCE& rhRef )
384
385{
387 bool matchFound = false;
388
389 if( refCol == -1 )
390 return false;
391
392 // First check the reference column. This can be done directly out of the
393 // SCH_REFERENCEs as the references can't be edited in the grid.
394 if( m_cols[refCol].m_group )
395 {
396 // if we're grouping by reference, then only the prefix must match
397 if( lhRef.GetRef() != rhRef.GetRef() )
398 return false;
399
400 matchFound = true;
401 }
402
403 const KIID& lhRefID = lhRef.GetSymbol()->m_Uuid;
404 const KIID& rhRefID = rhRef.GetSymbol()->m_Uuid;
405
406 // Now check all the other columns.
407 for( size_t i = 0; i < m_cols.size(); ++i )
408 {
409 //Handled already
410 if( (int) i == refCol )
411 continue;
412
413 if( !m_cols[i].m_group )
414 continue;
415
416 // If the field is a variable, we need to resolve it through the symbol
417 // to get the actual current value, otherwise we need to pull it out of the
418 // store so the refresh can regroup based on values that haven't been applied
419 // to the schematic yet.
420 wxString lh, rh;
421
422 if( IsTextVar( m_cols[i].m_fieldName )
423 || IsTextVar( m_dataStore[lhRefID][m_cols[i].m_fieldName] ) )
424 {
425 lh = getFieldShownText( lhRef, m_cols[i].m_fieldName );
426 }
427 else
428 lh = m_dataStore[lhRefID][m_cols[i].m_fieldName];
429
430 if( IsTextVar( m_cols[i].m_fieldName )
431 || IsTextVar( m_dataStore[rhRefID][m_cols[i].m_fieldName] ) )
432 {
433 rh = getFieldShownText( rhRef, m_cols[i].m_fieldName );
434 }
435 else
436 rh = m_dataStore[rhRefID][m_cols[i].m_fieldName];
437
438 wxString fieldName = m_cols[i].m_fieldName;
439
440 if( lh != rh )
441 return false;
442
443 matchFound = true;
444 }
445
446 return matchFound;
447}
448
449
451 const wxString& aFieldName )
452{
453 SCH_FIELD* field = aRef.GetSymbol()->GetFieldByName( aFieldName );
454
455 if( field )
456 return field->GetShownText( &aRef.GetSheetPath(), false );
457
458 // Handle fields with variables as names that are not present in the symbol
459 // by giving them the correct value by resolving against the symbol
460 if( IsTextVar( aFieldName ) )
461 {
462 int depth = 0;
463 const SCH_SHEET_PATH& path = aRef.GetSheetPath();
464
465 std::function<bool( wxString* )> symbolResolver =
466 [&]( wxString* token ) -> bool
467 {
468 return aRef.GetSymbol()->ResolveTextVar( &path, token, depth + 1 );
469 };
470
471 return ExpandTextVars( aFieldName, &symbolResolver );
472 }
473
474 return wxEmptyString;
475}
476
477
478bool FIELDS_EDITOR_GRID_DATA_MODEL::isAttribute( const wxString& aFieldName )
479{
480 return aFieldName == wxS( "${DNP}" )
481 || aFieldName == wxS( "${EXCLUDE_FROM_BOARD}" )
482 || aFieldName == wxS( "${EXCLUDE_FROM_BOM}" )
483 || aFieldName == wxS( "${EXCLUDE_FROM_SIM}" );
484}
485
486
488 const wxString& aAttributeName )
489{
490 if( aAttributeName == wxS( "${DNP}" ) )
491 return aSymbol.GetDNP() ? wxS( "1" ) : wxS( "0" );
492
493 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
494 return aSymbol.GetExcludedFromBoard() ? wxS( "1" ) : wxS( "0" );
495
496 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
497 return aSymbol.GetExcludedFromBOM() ? wxS( "1" ) : wxS( "0" );
498
499 if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
500 return aSymbol.GetExcludedFromSim() ? wxS( "1" ) : wxS( "0" );
501
502 return wxS( "0" );
503}
504
506 const wxString& aAttributeName,
507 const wxString& aValue )
508{
509 if( aAttributeName == wxS( "${DNP}" ) )
510 aSymbol.SetDNP( aValue == wxS( "1" ) );
511 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
512 aSymbol.SetExcludedFromBoard( aValue == wxS( "1" ) );
513 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
514 aSymbol.SetExcludedFromBOM( aValue == wxS( "1" ) );
515 else if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
516 aSymbol.SetExcludedFromSim( aValue == wxS( "1" ) );
517}
518
519
521{
522 m_rebuildsEnabled = true;
523}
524
525
527{
528 m_rebuildsEnabled = false;
529}
530
531
533{
534 if( !m_rebuildsEnabled )
535 return;
536
537 if( GetView() )
538 {
539 // Commit any pending in-place edits before the row gets moved out from under
540 // the editor.
541 static_cast<WX_GRID*>( GetView() )->CommitPendingChanges( true );
542
543 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
544 GetView()->ProcessTableMessage( msg );
545 }
546
547 m_rows.clear();
548
549 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
550 {
552
553 if( !m_filter.IsEmpty() && !WildCompareString( m_filter, ref.GetFullRef(), false ) )
554 continue;
555
556 if( m_excludeDNP && ref.GetSymbol()->GetDNP() )
557 continue;
558
560 continue;
561
562 // Check if the symbol if on the current sheet or, in the sheet path somewhere
563 // depending on scope
564 if( ( m_scope == SCOPE::SCOPE_SHEET && ref.GetSheetPath() != m_path )
565 || ( m_scope == SCOPE::SCOPE_SHEET_RECURSIVE
566 && !ref.GetSheetPath().IsContainedWithin( m_path ) ) )
567 {
568 continue;
569 }
570
571 bool matchFound = false;
572
573 // Performance optimization for ungrouped case to skip the N^2 for loop
574 if( !m_groupingEnabled && !ref.IsMultiUnit() )
575 {
576 m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
577 continue;
578 }
579
580 // See if we already have a row which this symbol fits into
581 for( DATA_MODEL_ROW& row : m_rows )
582 {
583 // all group members must have identical refs so just use the first one
584 SCH_REFERENCE rowRef = row.m_Refs[0];
585
586 if( unitMatch( ref, rowRef ) )
587 {
588 matchFound = true;
589 row.m_Refs.push_back( ref );
590 break;
591 }
592 else if( m_groupingEnabled && groupMatch( ref, rowRef ) )
593 {
594 matchFound = true;
595 row.m_Refs.push_back( ref );
596 row.m_Flag = GROUP_COLLAPSED;
597 break;
598 }
599 }
600
601 if( !matchFound )
602 m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
603 }
604
605 if( GetView() )
606 {
607 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() );
608 GetView()->ProcessTableMessage( msg );
609 }
610
611 Sort();
612}
613
614
616{
617 std::vector<DATA_MODEL_ROW> children;
618
619 for( SCH_REFERENCE& ref : m_rows[aRow].m_Refs )
620 {
621 bool matchFound = false;
622
623 // See if we already have a child group which this symbol fits into
624 for( DATA_MODEL_ROW& child : children )
625 {
626 // group members are by definition all matching, so just check
627 // against the first member
628 if( unitMatch( ref, child.m_Refs[0] ) )
629 {
630 matchFound = true;
631 child.m_Refs.push_back( ref );
632 break;
633 }
634 }
635
636 if( !matchFound )
637 children.emplace_back( DATA_MODEL_ROW( ref, CHILD_ITEM ) );
638 }
639
640 if( children.size() < 2 )
641 return;
642
643 std::sort( children.begin(), children.end(),
644 [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
645 {
646 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
647 } );
648
649 m_rows[aRow].m_Flag = GROUP_EXPANDED;
650 m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() );
651
652 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, children.size() );
653 GetView()->ProcessTableMessage( msg );
654}
655
656
658{
659 auto firstChild = m_rows.begin() + aRow + 1;
660 auto afterLastChild = firstChild;
661 int deleted = 0;
662
663 while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM )
664 {
665 deleted++;
666 afterLastChild++;
667 }
668
669 m_rows[aRow].m_Flag = GROUP_COLLAPSED;
670 m_rows.erase( firstChild, afterLastChild );
671
672 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted );
673 GetView()->ProcessTableMessage( msg );
674}
675
676
678{
679 DATA_MODEL_ROW& group = m_rows[aRow];
680
681 if( group.m_Flag == GROUP_COLLAPSED )
682 ExpandRow( aRow );
683 else if( group.m_Flag == GROUP_EXPANDED )
684 CollapseRow( aRow );
685}
686
687
689{
690 for( size_t i = 0; i < m_rows.size(); ++i )
691 {
692 if( m_rows[i].m_Flag == GROUP_EXPANDED )
693 {
694 CollapseRow( i );
696 }
697 }
698}
699
700
702{
703 for( size_t i = 0; i < m_rows.size(); ++i )
704 {
705 if( m_rows[i].m_Flag == GROUP_COLLAPSED_DURING_SORT )
706 ExpandRow( i );
707 }
708}
709
710
712 std::function<void( SCH_SYMBOL&, SCH_SHEET_PATH& )> symbolChangeHandler )
713{
714 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
715 {
716 SCH_SYMBOL& symbol = *m_symbolsList[i].GetSymbol();
717
718 symbolChangeHandler( symbol, m_symbolsList[i].GetSheetPath() );
719
720 const std::map<wxString, wxString>& fieldStore = m_dataStore[symbol.m_Uuid];
721
722 for( const std::pair<wxString, wxString> srcData : fieldStore )
723 {
724 const wxString& srcName = srcData.first;
725 const wxString& srcValue = srcData.second;
726
727 // Attributes bypass the field logic, so handle them first
728 if( isAttribute( srcName ) )
729 {
730 setAttributeValue( *m_symbolsList[i].GetSymbol(), srcName, srcValue );
731 continue;
732 }
733
734 // Skip special fields with variables as names (e.g. ${QUANTITY}),
735 // they can't be edited
736 if( IsTextVar( srcName ) )
737 continue;
738
739 SCH_FIELD* destField = symbol.FindField( srcName );
740 int col = GetFieldNameCol( srcName );
741 bool userAdded = ( col != -1 && m_cols[col].m_userAdded );
742
743 // Add a not existing field if it has a value for this symbol
744 bool createField = !destField && ( !srcValue.IsEmpty() || userAdded );
745
746 if( createField )
747 {
748 const VECTOR2I symbolPos = symbol.GetPosition();
749 destField = symbol.AddField( SCH_FIELD( symbolPos, -1, &symbol, srcName ) );
750 }
751
752 if( !destField )
753 continue;
754
755 if( destField->GetId() == REFERENCE_FIELD )
756 {
757 // Reference is not editable from this dialog
758 }
759 else if( destField->GetId() == VALUE_FIELD )
760 {
761 // Value field cannot be empty
762 if( !srcValue.IsEmpty() )
763 symbol.SetValueFieldText( srcValue );
764 }
765 else if( destField->GetId() == FOOTPRINT_FIELD )
766 {
767 symbol.SetFootprintFieldText( srcValue );
768 }
769 else
770 {
771 destField->SetText( srcValue );
772 }
773 }
774
775 for( int ii = symbol.GetFields().size() - 1; ii >= MANDATORY_FIELDS; ii-- )
776 {
777 if( fieldStore.count( symbol.GetFields()[ii].GetName() ) == 0 )
778 symbol.GetFields().erase( symbol.GetFields().begin() + ii );
779 }
780 }
781
782 m_edited = false;
783}
784
785
787{
788 int width = 0;
789
790 if( ColIsReference( aCol ) )
791 {
792 for( int row = 0; row < GetNumberRows(); ++row )
793 width = std::max( width, KIUI::GetTextSize( GetValue( row, aCol ), GetView() ).x );
794 }
795 else
796 {
797 wxString fieldName = GetColFieldName( aCol ); // symbol fieldName or Qty string
798
799 for( unsigned symbolRef = 0; symbolRef < m_symbolsList.GetCount(); ++symbolRef )
800 {
801 const KIID& symbolID = m_symbolsList[symbolRef].GetSymbol()->m_Uuid;
802 wxString text = m_dataStore[symbolID][fieldName];
803
804 width = std::max( width, KIUI::GetTextSize( text, GetView() ).x );
805 }
806 }
807
808 return width;
809}
810
811
813{
814 // Hide and un-group everything by default
815 for( size_t i = 0; i < m_cols.size(); i++ )
816 {
817 SetShowColumn( i, false );
818 SetGroupColumn( i, false );
819 }
820
821 std::vector<wxString> order;
822
823 // Set columns that are present and shown
824 for( BOM_FIELD field : aPreset.fieldsOrdered )
825 {
826 order.emplace_back( field.name );
827
828 int col = GetFieldNameCol( field.name );
829
830 // Add any missing fields, if the user doesn't add any data
831 // they won't be saved to the symbols anyway
832 if( col == -1 )
833 {
834 AddColumn( field.name, field.label, true );
835 col = GetFieldNameCol( field.name );
836 }
837 else
838 SetColLabelValue( col, field.label );
839
840 SetGroupColumn( col, field.groupBy );
841 SetShowColumn( col, field.show );
842 }
843
844 // Set grouping columns
846
847 SetFieldsOrder( order );
848
849 // Set our sorting
850 int sortCol = GetFieldNameCol( aPreset.sortField );
851
852 if( sortCol != -1 )
853 SetSorting( sortCol, aPreset.sortAsc );
854 else
856
857 SetFilter( aPreset.filterString );
858 SetExcludeDNP( aPreset.excludeDNP );
860
861 RebuildRows();
862}
863
864
866{
867 BOM_PRESET current;
868 current.readOnly = false;
870 current.sortField = GetColFieldName( GetSortCol() );
871 current.sortAsc = GetSortAsc();
872 current.filterString = GetFilter();
874 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:1138
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:108
SCH_FIELD * GetFieldByName(const wxString &aFieldName)
Return a field in this symbol.
Definition: sch_symbol.cpp:934
void SetValueFieldText(const wxString &aValue)
Definition: sch_symbol.cpp:888
SCH_FIELD * FindField(const wxString &aFieldName, bool aIncludeDefaultFields=true, bool aCaseInsensitive=false)
Search for a SCH_FIELD with aFieldName.
Definition: sch_symbol.cpp:993
void SetFootprintFieldText(const wxString &aFootprint)
Definition: sch_symbol.cpp:904
VECTOR2I GetPosition() const override
Definition: sch_symbol.h:782
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:973
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly)
Populate a std::vector with SCH_FIELDs.
Definition: sch_symbol.cpp:958
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:74
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:45