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