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