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