KiCad PCB EDA Suite
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
fields_data_model.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright (C) 2023 <author>
5 * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
6 *
7 * This program is free software: you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the
9 * Free Software Foundation, either version 3 of the License, or (at your
10 * option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20#include <wx/string.h>
21#include <wx/debug.h>
22#include <wx/grid.h>
23#include <common.h>
24#include <widgets/wx_grid.h>
25#include <sch_reference_list.h>
26#include <sch_commit.h>
27#include <sch_screen.h>
28#include "string_utils.h"
29
30#include "fields_data_model.h"
31
32
33const wxString FIELDS_EDITOR_GRID_DATA_MODEL::QUANTITY_VARIABLE = wxS( "${QUANTITY}" );
34const wxString FIELDS_EDITOR_GRID_DATA_MODEL::ITEM_NUMBER_VARIABLE = wxS( "${ITEM_NUMBER}" );
35
36
37void FIELDS_EDITOR_GRID_DATA_MODEL::AddColumn( const wxString& aFieldName, const wxString& aLabel,
38 bool aAddedByUser )
39{
40 // Don't add a field twice
41 if( GetFieldNameCol( aFieldName ) != -1 )
42 return;
43
44 m_cols.push_back( { aFieldName, aLabel, aAddedByUser, false, false } );
45
46 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
47 {
48 if( SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol() )
49 updateDataStoreSymbolField( *symbol, aFieldName );
50 }
51}
52
53
55 const wxString& aFieldName )
56{
57 if( isAttribute( aFieldName ) )
58 {
59 m_dataStore[aSymbol.m_Uuid][aFieldName] = getAttributeValue( aSymbol, aFieldName );
60 }
61 else if( const SCH_FIELD* field = aSymbol.GetField( aFieldName ) )
62 {
63 if( field->IsPrivate() )
64 {
65 m_dataStore[aSymbol.m_Uuid][aFieldName] = wxEmptyString;
66 return;
67 }
68
69 wxString value = aSymbol.Schematic()->ConvertKIIDsToRefs( field->GetText() );
70 m_dataStore[aSymbol.m_Uuid][aFieldName] = value;
71 }
72 else if( IsTextVar( aFieldName ) )
73 {
74 // Handle fields with variables as names that are not present in the symbol
75 // by giving them the correct value
76 m_dataStore[aSymbol.m_Uuid][aFieldName] = aFieldName;
77 }
78 else
79 {
80 m_dataStore[aSymbol.m_Uuid][aFieldName] = wxEmptyString;
81 }
82}
83
84
86{
87 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
88 {
89 if( SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol() )
90 m_dataStore[symbol->m_Uuid].erase( m_cols[aCol].m_fieldName );
91 }
92
93 m_cols.erase( m_cols.begin() + aCol );
94}
95
96
97void FIELDS_EDITOR_GRID_DATA_MODEL::RenameColumn( int aCol, const wxString& newName )
98{
99 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
100 {
101 SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol();
102
103 // Careful; field may have already been renamed from another sheet instance
104 if( auto node = m_dataStore[symbol->m_Uuid].extract( m_cols[aCol].m_fieldName ) )
105 {
106 node.key() = newName;
107 m_dataStore[symbol->m_Uuid].insert( std::move( node ) );
108 }
109 }
110
111 m_cols[aCol].m_fieldName = newName;
112 m_cols[aCol].m_label = newName;
113}
114
115
116int FIELDS_EDITOR_GRID_DATA_MODEL::GetFieldNameCol( const wxString& aFieldName ) const
117{
118 for( size_t i = 0; i < m_cols.size(); i++ )
119 {
120 if( m_cols[i].m_fieldName == aFieldName )
121 return static_cast<int>( i );
122 }
123
124 return -1;
125}
126
127
129{
130 std::vector<BOM_FIELD> fields;
131
132 for( const DATA_MODEL_COL& col : m_cols )
133 fields.push_back( { col.m_fieldName, col.m_label, col.m_show, col.m_group } );
134
135 return fields;
136}
137
138
139void FIELDS_EDITOR_GRID_DATA_MODEL::SetFieldsOrder( const std::vector<wxString>& aNewOrder )
140{
141 size_t foundCount = 0;
142
143 wxCHECK( aNewOrder.size() == m_cols.size(), /* void */ );
144
145 for( const wxString& newField : aNewOrder )
146 {
147 for( size_t i = 0; i < m_cols.size(); i++ )
148 {
149 if( m_cols[i].m_fieldName == newField )
150 {
151 std::swap( m_cols[foundCount], m_cols[i] );
152 foundCount++;
153 }
154 }
155 }
156}
157
158
159wxString FIELDS_EDITOR_GRID_DATA_MODEL::GetValue( int aRow, int aCol )
160{
161 if( ColIsReference( aCol ) )
162 {
163 // Poor-man's tree controls
164 if( m_rows[aRow].m_Flag == GROUP_COLLAPSED )
165 return wxT( "> " ) + GetValue( m_rows[aRow], aCol );
166 else if( m_rows[aRow].m_Flag == GROUP_EXPANDED )
167 return wxT( "v " ) + GetValue( m_rows[aRow], aCol );
168 else if( m_rows[aRow].m_Flag == CHILD_ITEM )
169 return wxT( " " ) + GetValue( m_rows[aRow], aCol );
170 else
171 return wxT( " " ) + GetValue( m_rows[aRow], aCol );
172 }
173 else
174 {
175 return GetValue( m_rows[aRow], aCol );
176 }
177}
178
179
180wxGridCellAttr* FIELDS_EDITOR_GRID_DATA_MODEL::GetAttr( int aRow, int aCol,
181 wxGridCellAttr::wxAttrKind aKind )
182{
183 if( GetColFieldName( aCol ) == GetCanonicalFieldName( FIELD_T::DATASHEET )
184 || IsURL( GetValue( m_rows[aRow], aCol ) ) )
185 {
186 if( m_urlEditor )
187 {
188 m_urlEditor->IncRef();
189 return enhanceAttr( m_urlEditor, aRow, aCol, aKind );
190 }
191 }
192
193 if( m_colAttrs[aCol] )
194 {
195 m_colAttrs[aCol]->IncRef();
196 return enhanceAttr( m_colAttrs[aCol], aRow, aCol, aKind );
197 }
198
199 return nullptr;
200}
201
202
204 const wxString& refDelimiter,
205 const wxString& refRangeDelimiter,
206 bool resolveVars,
207 bool listMixedValues )
208{
209 std::vector<SCH_REFERENCE> references;
210 std::set<wxString> mixedValues;
211 wxString fieldValue;
212
213 for( const SCH_REFERENCE& ref : group.m_Refs )
214 {
215 if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
216 {
217 references.push_back( ref );
218 }
219 else // Other columns are either a single value or ROW_MULTI_ITEMS
220 {
221 const KIID& symbolID = ref.GetSymbol()->m_Uuid;
222
223 if( !m_dataStore.count( symbolID )
224 || !m_dataStore[symbolID].count( m_cols[aCol].m_fieldName ) )
225 {
226 return INDETERMINATE_STATE;
227 }
228
229 wxString refFieldValue;
230
231 // Only resolve vars on actual variables, otherwise we want to get
232 // our values out of the datastore so we can show/export un-applied values
233 if( resolveVars
234 && ( IsTextVar( m_cols[aCol].m_fieldName )
235 || IsTextVar( m_dataStore[symbolID][m_cols[aCol].m_fieldName] ) ) )
236 {
237 refFieldValue = getFieldShownText( ref, m_cols[aCol].m_fieldName );
238 }
239 else
240 {
241 refFieldValue = m_dataStore[symbolID][m_cols[aCol].m_fieldName];
242 }
243
244 if( listMixedValues )
245 mixedValues.insert( refFieldValue );
246 else if( &ref == &group.m_Refs.front() )
247 fieldValue = refFieldValue;
248 else if( fieldValue != refFieldValue )
249 return INDETERMINATE_STATE;
250 }
251 }
252
253 if( listMixedValues )
254 {
255 fieldValue = wxEmptyString;
256
257 for( const wxString& value : mixedValues )
258 {
259 if( value.IsEmpty() )
260 continue;
261 else if( fieldValue.IsEmpty() )
262 fieldValue = value;
263 else
264 fieldValue += "," + value;
265 }
266 }
267
268 if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
269 {
270 // Remove duplicates (other units of multi-unit parts)
271 std::sort( references.begin(), references.end(),
272 []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
273 {
274 wxString l_ref( l.GetRef() << l.GetRefNumber() );
275 wxString r_ref( r.GetRef() << r.GetRefNumber() );
276 return StrNumCmp( l_ref, r_ref, true ) < 0;
277 } );
278
279 auto logicalEnd = std::unique( references.begin(), references.end(),
280 []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
281 {
282 // If unannotated then we can't tell what units belong together
283 // so we have to leave them all
284 if( l.GetRefNumber() == wxT( "?" ) )
285 return false;
286
287 wxString l_ref( l.GetRef() << l.GetRefNumber() );
288 wxString r_ref( r.GetRef() << r.GetRefNumber() );
289 return l_ref == r_ref;
290 } );
291
292 references.erase( logicalEnd, references.end() );
293 }
294
295 if( ColIsReference( aCol ) )
296 fieldValue = SCH_REFERENCE_LIST::Shorthand( references, refDelimiter, refRangeDelimiter );
297 else if( ColIsQuantity( aCol ) )
298 fieldValue = wxString::Format( wxT( "%d" ), (int) references.size() );
299 else if( ColIsItemNumber( aCol ) && group.m_Flag != CHILD_ITEM )
300 fieldValue = wxString::Format( wxT( "%d" ), group.m_ItemNumber );
301
302 return fieldValue;
303}
304
305
306void FIELDS_EDITOR_GRID_DATA_MODEL::SetValue( int aRow, int aCol, const wxString& aValue )
307{
308 wxCHECK_RET( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), wxS( "Invalid column number" ) );
309
310 // Can't modify references or text variables column, e.g. ${QUANTITY}
311 if( ColIsReference( aCol )
312 || ( IsTextVar( m_cols[aCol].m_fieldName ) && !ColIsAttribute( aCol ) ) )
313 {
314 return;
315 }
316
317 DATA_MODEL_ROW& rowGroup = m_rows[aRow];
318
319 for( const SCH_REFERENCE& ref : rowGroup.m_Refs )
320 m_dataStore[ref.GetSymbol()->m_Uuid][m_cols[aCol].m_fieldName] = aValue;
321
322 m_edited = true;
323}
324
325
327{
328 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
329 return m_cols[aCol].m_fieldName == GetCanonicalFieldName( FIELD_T::REFERENCE );
330}
331
332
334{
335 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
336 return m_cols[aCol].m_fieldName == GetCanonicalFieldName( FIELD_T::VALUE );
337}
338
339
341{
342 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
343 return m_cols[aCol].m_fieldName == QUANTITY_VARIABLE;
344}
345
346
348{
349 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
350 return m_cols[aCol].m_fieldName == ITEM_NUMBER_VARIABLE;
351}
352
353
355{
356 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
357 return isAttribute( m_cols[aCol].m_fieldName );
358}
359
360
362 const DATA_MODEL_ROW& rhGroup,
363 FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol,
364 bool ascending )
365{
366 // Empty rows always go to the bottom, whether ascending or descending
367 if( lhGroup.m_Refs.size() == 0 )
368 return true;
369 else if( rhGroup.m_Refs.size() == 0 )
370 return false;
371
372 // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
373 // to get the opposite sort. i.e. ~(a<b) != (a>b)
374 auto local_cmp =
375 [ ascending ]( const auto a, const auto b )
376 {
377 if( ascending )
378 return a < b;
379 else
380 return a > b;
381 };
382
383 // Primary sort key is sortCol; secondary is always REFERENCE (column 0)
384
385 wxString lhs = dataModel->GetValue( lhGroup, sortCol ).Trim( true ).Trim( false );
386 wxString rhs = dataModel->GetValue( rhGroup, sortCol ).Trim( true ).Trim( false );
387
388 if( lhs == rhs || dataModel->ColIsReference( sortCol ) )
389 {
390 wxString lhRef = lhGroup.m_Refs[0].GetRef() + lhGroup.m_Refs[0].GetRefNumber();
391 wxString rhRef = rhGroup.m_Refs[0].GetRef() + rhGroup.m_Refs[0].GetRefNumber();
392 return local_cmp( StrNumCmp( lhRef, rhRef, true ), 0 );
393 }
394 else
395 {
396 return local_cmp( ValueStringCompare( lhs, rhs ), 0 );
397 }
398}
399
400
402{
404
405 // We're going to sort the rows based on their first reference, so the first reference
406 // had better be the lowest one.
407 for( DATA_MODEL_ROW& row : m_rows )
408 {
409 std::sort( row.m_Refs.begin(), row.m_Refs.end(),
410 []( const SCH_REFERENCE& lhs, const SCH_REFERENCE& rhs )
411 {
412 wxString lhs_ref( lhs.GetRef() << lhs.GetRefNumber() );
413 wxString rhs_ref( rhs.GetRef() << rhs.GetRefNumber() );
414 return StrNumCmp( lhs_ref, rhs_ref, true ) < 0;
415 } );
416 }
417
418 std::sort( m_rows.begin(), m_rows.end(),
419 [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
420 {
421 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
422 } );
423
424 // Time to renumber the item numbers
425 int itemNumber = 1;
426 for( DATA_MODEL_ROW& row : m_rows )
427 {
428 row.m_ItemNumber = itemNumber++;
429 }
430
432}
433
434
436 const SCH_REFERENCE& rhRef )
437{
438 // If items are unannotated then we can't tell if they're units of the same symbol or not
439 if( lhRef.GetRefNumber() == wxT( "?" ) )
440 return false;
441
442 return ( lhRef.GetRef() == rhRef.GetRef() && lhRef.GetRefNumber() == rhRef.GetRefNumber() );
443}
444
445
447 const SCH_REFERENCE& rhRef )
448
449{
450 int refCol = GetFieldNameCol( GetCanonicalFieldName( FIELD_T::REFERENCE ) );
451 bool matchFound = false;
452
453 if( refCol == -1 )
454 return false;
455
456 // First check the reference column. This can be done directly out of the
457 // SCH_REFERENCEs as the references can't be edited in the grid.
458 if( m_cols[refCol].m_group )
459 {
460 // if we're grouping by reference, then only the prefix must match
461 if( lhRef.GetRef() != rhRef.GetRef() )
462 return false;
463
464 matchFound = true;
465 }
466
467 const KIID& lhRefID = lhRef.GetSymbol()->m_Uuid;
468 const KIID& rhRefID = rhRef.GetSymbol()->m_Uuid;
469
470 // Now check all the other columns.
471 for( size_t i = 0; i < m_cols.size(); ++i )
472 {
473 //Handled already
474 if( static_cast<int>( i ) == refCol )
475 continue;
476
477 if( !m_cols[i].m_group )
478 continue;
479
480 // If the field is a variable, we need to resolve it through the symbol
481 // to get the actual current value, otherwise we need to pull it out of the
482 // store so the refresh can regroup based on values that haven't been applied
483 // to the schematic yet.
484 wxString lh, rh;
485
486 if( IsTextVar( m_cols[i].m_fieldName )
487 || IsTextVar( m_dataStore[lhRefID][m_cols[i].m_fieldName] ) )
488 {
489 lh = getFieldShownText( lhRef, m_cols[i].m_fieldName );
490 }
491 else
492 {
493 lh = m_dataStore[lhRefID][m_cols[i].m_fieldName];
494 }
495
496 if( IsTextVar( m_cols[i].m_fieldName )
497 || IsTextVar( m_dataStore[rhRefID][m_cols[i].m_fieldName] ) )
498 {
499 rh = getFieldShownText( rhRef, m_cols[i].m_fieldName );
500 }
501 else
502 {
503 rh = m_dataStore[rhRefID][m_cols[i].m_fieldName];
504 }
505
506 wxString fieldName = m_cols[i].m_fieldName;
507
508 if( lh != rh )
509 return false;
510
511 matchFound = true;
512 }
513
514 return matchFound;
515}
516
517
519 const wxString& aFieldName )
520{
521 SCH_FIELD* field = aRef.GetSymbol()->GetField( aFieldName );
522
523 if( field )
524 {
525 if( field->IsPrivate() )
526 return wxEmptyString;
527 else
528 return field->GetShownText( &aRef.GetSheetPath(), false );
529 }
530
531 // Handle fields with variables as names that are not present in the symbol
532 // by giving them the correct value by resolving against the symbol
533 if( IsTextVar( aFieldName ) )
534 {
535 int depth = 0;
536 const SCH_SHEET_PATH& path = aRef.GetSheetPath();
537
538 std::function<bool( wxString* )> symbolResolver =
539 [&]( wxString* token ) -> bool
540 {
541 return aRef.GetSymbol()->ResolveTextVar( &path, token, depth + 1 );
542 };
543
544 return ExpandTextVars( aFieldName, &symbolResolver );
545 }
546
547 return wxEmptyString;
548}
549
550
551bool FIELDS_EDITOR_GRID_DATA_MODEL::isAttribute( const wxString& aFieldName )
552{
553 return aFieldName == wxS( "${DNP}" )
554 || aFieldName == wxS( "${EXCLUDE_FROM_BOARD}" )
555 || aFieldName == wxS( "${EXCLUDE_FROM_BOM}" )
556 || aFieldName == wxS( "${EXCLUDE_FROM_SIM}" );
557}
558
559
561 const wxString& aAttributeName )
562{
563 if( aAttributeName == wxS( "${DNP}" ) )
564 return aSymbol.GetDNP() ? wxS( "1" ) : wxS( "0" );
565
566 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
567 return aSymbol.GetExcludedFromBoard() ? wxS( "1" ) : wxS( "0" );
568
569 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
570 return aSymbol.GetExcludedFromBOM() ? wxS( "1" ) : wxS( "0" );
571
572 if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
573 return aSymbol.GetExcludedFromSim() ? wxS( "1" ) : wxS( "0" );
574
575 return wxS( "0" );
576}
577
579 const wxString& aAttributeName,
580 const wxString& aValue )
581{
582 if( aAttributeName == wxS( "${DNP}" ) )
583 aSymbol.SetDNP( aValue == wxS( "1" ) );
584 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
585 aSymbol.SetExcludedFromBoard( aValue == wxS( "1" ) );
586 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
587 aSymbol.SetExcludedFromBOM( aValue == wxS( "1" ) );
588 else if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
589 aSymbol.SetExcludedFromSim( aValue == wxS( "1" ) );
590}
591
592
594{
595 m_rebuildsEnabled = true;
596}
597
598
600{
601 m_rebuildsEnabled = false;
602}
603
604
606{
607 if( !m_rebuildsEnabled )
608 return;
609
610 if( GetView() )
611 {
612 // Commit any pending in-place edits before the row gets moved out from under
613 // the editor.
614 static_cast<WX_GRID*>( GetView() )->CommitPendingChanges( true );
615
616 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
617 GetView()->ProcessTableMessage( msg );
618 }
619
620 m_rows.clear();
621
622 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
623 {
625
626 if( !m_filter.IsEmpty() && !WildCompareString( m_filter, ref.GetFullRef(), false ) )
627 continue;
628
629 if( m_excludeDNP && ( ref.GetSymbol()->GetDNP()
630 || ref.GetSheetPath().GetDNP() ) )
631 {
632 continue;
633 }
634
636 || ref.GetSheetPath().GetExcludedFromBOM() ) )
637 {
638 continue;
639 }
640
641 // Check if the symbol if on the current sheet or, in the sheet path somewhere
642 // depending on scope
643 if( ( m_scope == SCOPE::SCOPE_SHEET && ref.GetSheetPath() != m_path )
644 || ( m_scope == SCOPE::SCOPE_SHEET_RECURSIVE
645 && !ref.GetSheetPath().IsContainedWithin( m_path ) ) )
646 {
647 continue;
648 }
649
650 bool matchFound = false;
651
652 // Performance optimization for ungrouped case to skip the N^2 for loop
653 if( !m_groupingEnabled && !ref.IsMultiUnit() )
654 {
655 m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
656 continue;
657 }
658
659 // See if we already have a row which this symbol fits into
660 for( DATA_MODEL_ROW& row : m_rows )
661 {
662 // all group members must have identical refs so just use the first one
663 SCH_REFERENCE rowRef = row.m_Refs[0];
664
665 if( unitMatch( ref, rowRef ) )
666 {
667 matchFound = true;
668 row.m_Refs.push_back( ref );
669 break;
670 }
671 else if( m_groupingEnabled && groupMatch( ref, rowRef ) )
672 {
673 matchFound = true;
674 row.m_Refs.push_back( ref );
675 row.m_Flag = GROUP_COLLAPSED;
676 break;
677 }
678 }
679
680 if( !matchFound )
681 m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
682 }
683
684 if( GetView() )
685 {
686 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() );
687 GetView()->ProcessTableMessage( msg );
688 }
689
690 Sort();
691}
692
693
695{
696 std::vector<DATA_MODEL_ROW> children;
697
698 for( SCH_REFERENCE& ref : m_rows[aRow].m_Refs )
699 {
700 bool matchFound = false;
701
702 // See if we already have a child group which this symbol fits into
703 for( DATA_MODEL_ROW& child : children )
704 {
705 // group members are by definition all matching, so just check
706 // against the first member
707 if( unitMatch( ref, child.m_Refs[0] ) )
708 {
709 matchFound = true;
710 child.m_Refs.push_back( ref );
711 break;
712 }
713 }
714
715 if( !matchFound )
716 children.emplace_back( DATA_MODEL_ROW( ref, CHILD_ITEM ) );
717 }
718
719 if( children.size() < 2 )
720 return;
721
722 std::sort( children.begin(), children.end(),
723 [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
724 {
725 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
726 } );
727
728 m_rows[aRow].m_Flag = GROUP_EXPANDED;
729 m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() );
730
731 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, children.size() );
732 GetView()->ProcessTableMessage( msg );
733}
734
735
737{
738 auto firstChild = m_rows.begin() + aRow + 1;
739 auto afterLastChild = firstChild;
740 int deleted = 0;
741
742 while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM )
743 {
744 deleted++;
745 afterLastChild++;
746 }
747
748 m_rows[aRow].m_Flag = GROUP_COLLAPSED;
749 m_rows.erase( firstChild, afterLastChild );
750
751 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted );
752 GetView()->ProcessTableMessage( msg );
753}
754
755
757{
758 DATA_MODEL_ROW& group = m_rows[aRow];
759
760 if( group.m_Flag == GROUP_COLLAPSED )
761 ExpandRow( aRow );
762 else if( group.m_Flag == GROUP_EXPANDED )
763 CollapseRow( aRow );
764}
765
766
768{
769 for( size_t i = 0; i < m_rows.size(); ++i )
770 {
771 if( m_rows[i].m_Flag == GROUP_EXPANDED )
772 {
773 CollapseRow( i );
775 }
776 }
777}
778
779
781{
782 for( size_t i = 0; i < m_rows.size(); ++i )
783 {
784 if( m_rows[i].m_Flag == GROUP_COLLAPSED_DURING_SORT )
785 ExpandRow( i );
786 }
787}
788
789
791{
792
793 for( const SCH_REFERENCE& instance : m_symbolsList )
794 {
795 SCH_SYMBOL& symbol = *instance.GetSymbol();
796 SCHEMATIC_SETTINGS& settings = symbol.Schematic()->Settings();
797
798 aCommit.Modify( &symbol, instance.GetSheetPath().LastScreen() );
799
800 const std::map<wxString, wxString>& fieldStore = m_dataStore[symbol.m_Uuid];
801
802 for( const auto& [srcName, srcValue] : fieldStore )
803 {
804 // Attributes bypass the field logic, so handle them first
805 if( isAttribute( srcName ) )
806 {
807 setAttributeValue( symbol, srcName, srcValue );
808 continue;
809 }
810
811 // Skip special fields with variables as names (e.g. ${QUANTITY}),
812 // they can't be edited
813 if( IsTextVar( srcName ) )
814 continue;
815
816 SCH_FIELD* destField = symbol.GetField( srcName );
817
818 if( destField && destField->IsPrivate() )
819 {
820 if( srcValue.IsEmpty() )
821 continue;
822 else
823 destField->SetPrivate( false );
824 }
825
826 int col = GetFieldNameCol( srcName );
827 bool userAdded = ( col != -1 && m_cols[col].m_userAdded );
828
829 // Add a not existing field if it has a value for this symbol
830 bool createField = !destField && ( !srcValue.IsEmpty() || userAdded );
831
832 if( createField )
833 {
834 destField = symbol.AddField( SCH_FIELD( symbol.GetPosition(), FIELD_T::USER,
835 &symbol, srcName ) );
836 destField->SetTextAngle( symbol.GetField( FIELD_T::REFERENCE )->GetTextAngle() );
837 destField->SetTextSize( VECTOR2I( settings.m_DefaultTextSize,
838 settings.m_DefaultTextSize ) );
839 destField->SetVisible( false );
840 }
841
842 if( !destField )
843 continue;
844
845 if( destField->GetId() == FIELD_T::REFERENCE )
846 {
847 // Reference is not editable from this dialog
848 continue;
849 }
850
851 destField->SetText( symbol.Schematic()->ConvertRefsToKIIDs( srcValue ) );
852 }
853
854 for( int ii = static_cast<int>( symbol.GetFields().size() ) - 1; ii >= 0; ii-- )
855 {
856 if( symbol.GetFields()[ii].IsMandatory() || symbol.GetFields()[ii].IsPrivate() )
857 continue;
858
859 if( fieldStore.count( symbol.GetFields()[ii].GetName() ) == 0 )
860 symbol.GetFields().erase( symbol.GetFields().begin() + ii );
861 }
862 }
863
864 m_edited = false;
865}
866
867
869{
870 int width = 0;
871
872 if( ColIsReference( aCol ) )
873 {
874 for( int row = 0; row < GetNumberRows(); ++row )
875 width = std::max( width, KIUI::GetTextSize( GetValue( row, aCol ), GetView() ).x );
876 }
877 else
878 {
879 wxString fieldName = GetColFieldName( aCol ); // symbol fieldName or Qty string
880
881 for( unsigned symbolRef = 0; symbolRef < m_symbolsList.GetCount(); ++symbolRef )
882 {
883 const KIID& symbolID = m_symbolsList[symbolRef].GetSymbol()->m_Uuid;
884 wxString text = m_dataStore[symbolID][fieldName];
885
886 width = std::max( width, KIUI::GetTextSize( text, GetView() ).x );
887 }
888 }
889
890 return width;
891}
892
893
895{
896 // Hide and un-group everything by default
897 for( size_t i = 0; i < m_cols.size(); i++ )
898 {
899 SetShowColumn( i, false );
900 SetGroupColumn( i, false );
901 }
902
903 std::set<wxString> seen;
904 std::vector<wxString> order;
905
906 // Set columns that are present and shown
907 for( const BOM_FIELD& field : aPreset.fieldsOrdered )
908 {
909 // Ignore empty fields
910 if( !field.name || seen.count( field.name ) )
911 continue;
912
913 seen.insert( field.name );
914 order.emplace_back( field.name );
915
916 int col = GetFieldNameCol( field.name );
917
918 // Add any missing fields, if the user doesn't add any data
919 // they won't be saved to the symbols anyway
920 if( col == -1 )
921 {
922 AddColumn( field.name, field.label, true );
923 col = GetFieldNameCol( field.name );
924 }
925 else
926 {
927 SetColLabelValue( col, field.label );
928 }
929
930 SetGroupColumn( col, field.groupBy );
931 SetShowColumn( col, field.show );
932 }
933
934 // Set grouping columns
936
937 SetFieldsOrder( order );
938
939 // Set our sorting
940 int sortCol = GetFieldNameCol( aPreset.sortField );
941
942 if( sortCol == -1 )
943 sortCol = GetFieldNameCol( GetCanonicalFieldName( FIELD_T::REFERENCE ) );
944
945 SetSorting( sortCol, aPreset.sortAsc );
946
947 SetFilter( aPreset.filterString );
948 SetExcludeDNP( aPreset.excludeDNP );
950
951 RebuildRows();
952}
953
954
956{
957 BOM_PRESET current;
958 current.readOnly = false;
960 current.sortField = GetColFieldName( GetSortCol() );
961 current.sortAsc = GetSortAsc();
962 current.filterString = GetFilter();
964 current.excludeDNP = GetExcludeDNP();
966
967 return current;
968}
969
970
972{
973 wxString out;
974
975 if( m_cols.empty() )
976 return out;
977
978 int last_col = -1;
979
980 // Find the location for the line terminator
981 for( size_t col = 0; col < m_cols.size(); col++ )
982 {
983 if( m_cols[col].m_show )
984 last_col = static_cast<int>( col );
985 }
986
987 // No shown columns
988 if( last_col == -1 )
989 return out;
990
991 auto formatField =
992 [&]( wxString field, bool last ) -> wxString
993 {
994 if( !settings.keepLineBreaks )
995 {
996 field.Replace( wxS( "\r" ), wxS( "" ) );
997 field.Replace( wxS( "\n" ), wxS( "" ) );
998 }
999
1000 if( !settings.keepTabs )
1001 {
1002 field.Replace( wxS( "\t" ), wxS( "" ) );
1003 }
1004
1005 if( !settings.stringDelimiter.IsEmpty() )
1006 {
1007 field.Replace( settings.stringDelimiter,
1008 settings.stringDelimiter + settings.stringDelimiter );
1009 }
1010
1011 return settings.stringDelimiter + field + settings.stringDelimiter
1012 + ( last ? wxString( wxS( "\n" ) ) : settings.fieldDelimiter );
1013 };
1014
1015 // Column names
1016 for( size_t col = 0; col < m_cols.size(); col++ )
1017 {
1018 if( !m_cols[col].m_show )
1019 continue;
1020
1021 out.Append( formatField( m_cols[col].m_label, col == static_cast<size_t>( last_col ) ) );
1022 }
1023
1024 // Data rows
1025 for( size_t row = 0; row < m_rows.size(); row++ )
1026 {
1027 // Don't output child rows
1028 if( GetRowFlags( static_cast<int>( row ) ) == CHILD_ITEM )
1029 continue;
1030
1031 for( size_t col = 0; col < m_cols.size(); col++ )
1032 {
1033 if( !m_cols[col].m_show )
1034 continue;
1035
1036 // Get the unannotated version of the field, e.g. no "> " or "v " by
1037 out.Append( formatField( GetExportValue( static_cast<int>( row ), static_cast<int>( col ),
1038 settings.refDelimiter, settings.refRangeDelimiter ),
1039 col == static_cast<size_t>( last_col ) ) );
1040 }
1041 }
1042
1043 return out;
1044}
1045
1046
1048{
1049 for( const SCH_REFERENCE& ref : aRefs )
1050 {
1051 if( !m_symbolsList.Contains( ref ) )
1052 {
1053 SCH_SYMBOL* symbol = ref.GetSymbol();
1054
1055 m_symbolsList.AddItem( ref );
1056
1057 // Update the fields of every reference
1058 for( const SCH_FIELD& field : symbol->GetFields() )
1059 {
1060 if( !field.IsPrivate() )
1061 {
1062 wxString name = field.GetCanonicalName();
1063 wxString value = symbol->Schematic()->ConvertKIIDsToRefs( field.GetText() );
1064
1065 m_dataStore[symbol->m_Uuid][name] = value;
1066 }
1067 }
1068 }
1069 }
1070}
1071
1072
1074{
1075 // The schematic event listener passes us the symbol after it has been removed,
1076 // so we can't just work with a SCH_REFERENCE_LIST like the other handlers as the
1077 // references are already gone. Instead we need to prune our list.
1078 m_dataStore[aSymbol.m_Uuid].clear();
1079
1080 // Remove all refs that match this symbol using remove_if
1082 [&aSymbol]( const SCH_REFERENCE& ref ) -> bool
1083 {
1084 return ref.GetSymbol()->m_Uuid == aSymbol.m_Uuid;
1085 } ),
1086 m_symbolsList.end() );
1087}
1088
1089
1091{
1092 for( const SCH_REFERENCE& ref : aRefs )
1093 {
1094 int index = m_symbolsList.FindRefByFullPath( ref.GetFullPath() );
1095
1096 if( index != -1 )
1097 {
1098 m_symbolsList.RemoveItem( index );
1099
1100 // If we're out of instances then remove the symbol, too
1101 if( ref.GetSymbol()->GetInstances().empty() )
1102 m_dataStore.erase( ref.GetSymbol()->m_Uuid );
1103 }
1104 }
1105}
1106
1107
1109{
1110 for( const SCH_REFERENCE& ref : aRefs )
1111 {
1112 // Update the fields of every reference. Do this by iterating through the data model
1113 // columns; we must have all fields in the symbol added to the data model at this point,
1114 // and some of the data model columns may be variables that are not present in the symbol
1115 for( const DATA_MODEL_COL& col : m_cols )
1116 updateDataStoreSymbolField( *ref.GetSymbol(), col.m_fieldName );
1117
1118 if( !m_symbolsList.Contains( ref ) )
1119 m_symbolsList.AddItem( ref );
1120 }
1121}
const char * name
Definition: DXF_plotter.cpp:59
COMMIT & Modify(EDA_ITEM *aItem, BASE_SCREEN *aScreen=nullptr)
Modify a given item in the model.
Definition: commit.h:108
const KIID m_Uuid
Definition: eda_item.h:494
void SetTextSize(VECTOR2I aNewSize, bool aEnforceMinTextSize=true)
Definition: eda_text.cpp:526
virtual void SetVisible(bool aVisible)
Definition: eda_text.cpp:379
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition: eda_text.cpp:292
int GetFieldNameCol(const wxString &aFieldName) const
wxString GetColFieldName(int aCol)
std::vector< DATA_MODEL_ROW > m_rows
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)
wxGridCellAttr * GetAttr(int aRow, int aCol, wxGridCellAttr::wxAttrKind aKind) override
void SetFilter(const wxString &aFilter)
std::map< int, wxGridCellAttr * > m_colAttrs
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(SCH_COMMIT &aCommit)
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)
std::vector< BOM_FIELD > GetFieldsOrdered()
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)
Definition: kiid.h:49
These are loaded from Eeschema settings but then overwritten by the project settings.
SCHEMATIC_SETTINGS & Settings() const
Definition: schematic.cpp:306
wxString ConvertKIIDsToRefs(const wxString &aSource) const
Definition: schematic.cpp:575
wxString ConvertRefsToKIIDs(const wxString &aSource) const
Definition: schematic.cpp:505
FIELD_T GetId() const
Definition: sch_field.h:124
wxString GetShownText(const SCH_SHEET_PATH *aPath, bool aAllowExtraText, int aDepth=0) const
Definition: sch_field.cpp:192
void SetText(const wxString &aText) override
Definition: sch_field.cpp:1069
void SetPrivate(bool aPrivate)
Definition: sch_item.h:245
SCHEMATIC * Schematic() const
Search the item hierarchy to find a SCHEMATIC.
Definition: sch_item.cpp:156
bool IsPrivate() const
Definition: sch_item.h:246
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
Return reference name with unit altogether.
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 GetExcludedFromBOM() const
bool IsContainedWithin(const SCH_SHEET_PATH &aSheetPathToTest) const
Check if this path is contained inside aSheetPathToTest.
bool GetDNP() const
Schematic symbol object.
Definition: sch_symbol.h:75
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly) const override
Populate a std::vector with SCH_FIELDs, sorted in ordinal order.
Definition: sch_symbol.cpp:841
VECTOR2I GetPosition() const override
Definition: sch_symbol.h:771
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:868
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
Definition: sch_symbol.cpp:813
void SetDNP(bool aDNP)
Definition: symbol.h:193
bool GetExcludedFromBoard() const
Definition: symbol.h:187
bool GetExcludedFromBOM() const
Definition: symbol.h:181
void SetExcludedFromSim(bool aExcludeFromSim) override
Set or clear the exclude from simulation flag.
Definition: symbol.h:170
bool GetDNP() const
Set or clear the 'Do Not Populate' flag.
Definition: symbol.h:192
void SetExcludedFromBOM(bool aExcludeFromBOM)
Set or clear the exclude from schematic bill of materials flag.
Definition: symbol.h:180
void SetExcludedFromBoard(bool aExcludeFromBoard)
Set or clear exclude from board netlist flag.
Definition: symbol.h:186
bool GetExcludedFromSim() const override
Definition: symbol.h:175
wxGridCellAttr * enhanceAttr(wxGridCellAttr *aInputAttr, int aRow, int aCol, wxGridCellAttr::wxAttrKind aKind)
Definition: wx_grid.cpp:45
wxString ExpandTextVars(const wxString &aSource, const PROJECT *aProject, int aFlags)
Definition: common.cpp:59
bool IsTextVar(const wxString &aSource)
Returns true if the string is a text var, e.g starts with ${.
Definition: common.cpp:133
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:78
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...
bool IsURL(wxString aStr)
Performs a URL sniff-test on a string.
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(FIELD_T aFieldType)
#define INDETERMINATE_STATE
Used for holding indeterminate values, such as with multiple selections holding different values or c...
Definition: ui_common.h:46
VECTOR2< int32_t > VECTOR2I
Definition: vector2d.h:695