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