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
18 * along with this program. If not, see <https://www.gnu.org/licenses/>.
19 */
20#include <nlohmann/json.hpp>
21#include <wx/string.h>
22#include <wx/debug.h>
23#include <wx/grid.h>
24#include <wx/settings.h>
25#include <common.h>
26#include <widgets/wx_grid.h>
27#include <sch_reference_list.h>
28#include <sch_commit.h>
29#include <sch_screen.h>
30#include <template_fieldnames.h>
31#include <sch_sheet_path.h>
32#include "string_utils.h"
33
34#include "fields_data_model.h"
35
36
42class GRID_CELL_RESOLVED_TEXT_RENDERER : public wxGridCellStringRenderer
43{
44public:
46 wxGridCellStringRenderer()
47 {
48 }
49
50 void Draw( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC& aDC, const wxRect& aRect, int aRow, int aCol,
51 bool isSelected ) override
52 {
53 wxString value = aGrid.GetCellValue( aRow, aCol );
54
55 if( auto* model = dynamic_cast<FIELDS_EDITOR_GRID_DATA_MODEL*>( aGrid.GetTable() ) )
56 value = model->GetResolvedValue( aRow, aCol );
57
58 wxRect rect = aRect;
59 rect.Inflate( -1 );
60
61 wxGridCellRenderer::Draw( aGrid, aAttr, aDC, aRect, aRow, aCol, isSelected );
62 SetTextColoursAndFont( aGrid, aAttr, aDC, isSelected );
63 aGrid.DrawTextRectangle( aDC, value, rect, wxALIGN_LEFT, wxALIGN_CENTRE );
64 }
65
66 wxSize GetBestSize( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC& aDC, int aRow, int aCol ) override
67 {
68 wxString value = aGrid.GetCellValue( aRow, aCol );
69
70 if( auto* model = dynamic_cast<FIELDS_EDITOR_GRID_DATA_MODEL*>( aGrid.GetTable() ) )
71 value = model->GetResolvedValue( aRow, aCol );
72
73 return wxGridCellStringRenderer::DoGetBestSize( aAttr, aDC, value );
74 }
75
76 wxGridCellRenderer* Clone() const override { return new GRID_CELL_RESOLVED_TEXT_RENDERER(); }
77};
78
79
88static KIID_PATH makeDataStoreKey( const SCH_SHEET_PATH& aSheetPath, const SCH_SYMBOL& aSymbol )
89{
90 KIID_PATH path = aSheetPath.Path();
91 path.push_back( aSymbol.m_Uuid );
92 return path;
93}
94
95
96const wxString FIELDS_EDITOR_GRID_DATA_MODEL::QUANTITY_VARIABLE = wxS( "${QUANTITY}" );
97const wxString FIELDS_EDITOR_GRID_DATA_MODEL::ITEM_NUMBER_VARIABLE = wxS( "${ITEM_NUMBER}" );
98
99
100void FIELDS_EDITOR_GRID_DATA_MODEL::AddColumn( const wxString& aFieldName, const wxString& aLabel, bool aAddedByUser )
101{
102 // Don't add a field twice
103 if( GetFieldNameCol( aFieldName ) != -1 )
104 return;
105
106 m_cols.push_back( { aFieldName, aLabel, aAddedByUser, false, false } );
107
108 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
110}
111
112
114 const wxString& aFieldName )
115{
116 const SCH_SYMBOL* symbol = aSymbolRef.GetSymbol();
117
118 if( !symbol )
119 return;
120
121 KIID_PATH key = makeDataStoreKey( aSymbolRef.GetSheetPath(), *symbol );
122
123 if( isAttribute( aFieldName ) )
124 {
125 m_dataStore[key][aFieldName] = getAttributeValue( aSymbolRef, aFieldName, m_currentVariant );
126 }
127 else if( const SCH_FIELD* field = symbol->GetField( aFieldName ) )
128 {
129 if( field->IsPrivate() )
130 {
131 m_dataStore[key][aFieldName] = wxEmptyString;
132 return;
133 }
134
135 wxString value = symbol->Schematic()->ConvertKIIDsToRefs(
136 field->GetText( &aSymbolRef.GetSheetPath(), m_currentVariant ) );
137
138 m_dataStore[key][aFieldName] = value;
139 }
140 else if( IsGeneratedField( aFieldName ) )
141 {
142 // Handle generated fields with variables as names (e.g. ${QUANTITY}) that are not present in
143 // the symbol by giving them the correct value
144 m_dataStore[key][aFieldName] = aFieldName;
145 }
146 else
147 {
148 m_dataStore[key][aFieldName] = wxEmptyString;
149 }
150}
151
152
154{
155 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
156 {
157 if( SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol() )
158 {
159 KIID_PATH key = makeDataStoreKey( m_symbolsList[i].GetSheetPath(), *symbol );
160 m_dataStore[key].erase( m_cols[aCol].m_fieldName );
161 }
162 }
163
164 m_cols.erase( m_cols.begin() + aCol );
165
166 if( wxGrid* grid = GetView() )
167 {
168 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_COLS_DELETED, aCol, 1 );
169 grid->ProcessTableMessage( msg );
170 }
171}
172
173
174void FIELDS_EDITOR_GRID_DATA_MODEL::RenameColumn( int aCol, const wxString& newName )
175{
176 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
177 {
178 SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol();
179 KIID_PATH key = makeDataStoreKey( m_symbolsList[i].GetSheetPath(), *symbol );
180
181 // Careful; field may have already been renamed from another sheet instance
182 if( auto node = m_dataStore[key].extract( m_cols[aCol].m_fieldName ) )
183 {
184 node.key() = newName;
185 m_dataStore[key].insert( std::move( node ) );
186 }
187 }
188
189 m_cols[aCol].m_fieldName = newName;
190 m_cols[aCol].m_label = newName;
191}
192
193
194int FIELDS_EDITOR_GRID_DATA_MODEL::GetFieldNameCol( const wxString& aFieldName ) const
195{
196 for( size_t i = 0; i < m_cols.size(); i++ )
197 {
198 if( FieldNamesAreDuplicates( m_cols[i].m_fieldName, aFieldName ) )
199 return static_cast<int>( i );
200 }
201
202 return -1;
203}
204
205
207{
208 std::vector<BOM_FIELD> fields;
209
210 for( const DATA_MODEL_COL& col : m_cols )
211 fields.push_back( { col.m_fieldName, col.m_label, col.m_show, col.m_group } );
212
213 return fields;
214}
215
216
217void FIELDS_EDITOR_GRID_DATA_MODEL::SetFieldsOrder( const std::vector<wxString>& aNewOrder )
218{
219 size_t foundCount = 0;
220
221 for( const wxString& newField : aNewOrder )
222 {
223 if( foundCount >= m_cols.size() )
224 break;
225
226 for( DATA_MODEL_COL& col : m_cols )
227 {
228 if( col.m_fieldName == newField )
229 {
230 std::swap( m_cols[foundCount], col );
231 foundCount++;
232 break;
233 }
234 }
235 }
236}
237
238
240{
241 // Check if aCol is the first visible column
242 for( int col = 0; col < aCol; ++col )
243 {
244 if( m_cols[col].m_show )
245 return false;
246 }
247
248 return true;
249}
250
251
252wxString FIELDS_EDITOR_GRID_DATA_MODEL::GetValue( int aRow, int aCol )
253{
254 GetView()->SetReadOnly( aRow, aCol,
255 IsExpanderColumn( aCol ) || rowAttributeInheritedFromSheet( m_rows[aRow], aCol ) );
256 return GetValue( m_rows[aRow], aCol );
257}
258
259
261{
262 return GetValue( m_rows[aRow], aCol, wxT( ", " ), wxT( "-" ), true, false );
263}
264
265
266wxGridCellAttr* FIELDS_EDITOR_GRID_DATA_MODEL::GetAttr( int aRow, int aCol, wxGridCellAttr::wxAttrKind aKind )
267{
268 wxGridCellAttr* attr = nullptr;
269 bool needsUrlEditor = false;
270 bool needsVariantHighlight = false;
271 bool needsTextVarRenderer = false;
272 wxColour highlightColor;
273
274 // Check if we need URL editor
276 || IsURL( GetValue( m_rows[aRow], aCol ) ) )
277 {
278 if( m_urlEditor )
279 needsUrlEditor = true;
280 }
281
282 // Check if the raw value contains a text variable that should be resolved for display
283 if( aRow >= 0 && aRow < (int) m_rows.size() && aCol >= 0 && aCol < (int) m_cols.size() && !ColIsReference( aCol )
284 && !ColIsQuantity( aCol ) && !ColIsItemNumber( aCol ) )
285 {
286 wxString rawValue = GetValue( m_rows[aRow], aCol );
287
288 if( rawValue.Contains( wxT( "${" ) ) )
289 needsTextVarRenderer = true;
290 }
291
292 // Check if we need variant highlighting
293 if( !m_currentVariant.IsEmpty() && aRow >= 0 && aRow < (int) m_rows.size()
294 && aCol >= 0 && aCol < (int) m_cols.size() )
295 {
296 const wxString& fieldName = m_cols[aCol].m_fieldName;
297
298 // Skip Reference and generated fields (like ${QUANTITY}) for highlighting
299 if( !ColIsReference( aCol ) && !ColIsQuantity( aCol ) && !ColIsItemNumber( aCol ) )
300 {
301 const DATA_MODEL_ROW& row = m_rows[aRow];
302
303 // Check if any symbol in this row has a variant-specific value
304 for( const SCH_REFERENCE& ref : row.m_Refs )
305 {
306 wxString defaultValue = getDefaultFieldValue( ref, fieldName );
307
308 KIID_PATH symbolKey = KIID_PATH();
309
310 if( const SCH_SYMBOL* symbol = ref.GetSymbol() )
311 {
312 symbolKey = ref.GetSheetPath().Path();
313 symbolKey.push_back( symbol->m_Uuid );
314 }
315
316 // Get the current value from the data store
317 wxString currentValue;
318
319 if( m_dataStore.contains( symbolKey ) && m_dataStore[symbolKey].contains( fieldName ) )
320 currentValue = m_dataStore[symbolKey][fieldName];
321
322 if( currentValue != defaultValue )
323 {
324 needsVariantHighlight = true;
325
326 bool isPriority2 = false;
327
328 if( const SCH_SYMBOL* sym = ref.GetSymbol() )
329 {
330 auto variantData = sym->GetVariant( ref.GetSheetPath(),
332
333 if( variantData
334 && variantData->m_SymbolOverride
335 && !variantData->m_Fields.count( fieldName ) )
336 {
337 isPriority2 = true;
338 }
339 }
340
341 wxColour bg = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW );
342 bool isDark = ( bg.Red() + bg.Green() + bg.Blue() ) < 384;
343
344 if( isPriority2 )
345 {
346 highlightColor = isDark ? wxColour( 40, 60, 80 )
347 : wxColour( 220, 235, 255 );
348 }
349 else
350 {
351 highlightColor = isDark ? wxColour( 80, 80, 40 )
352 : wxColour( 255, 255, 200 );
353 }
354
355 break;
356 }
357 }
358 }
359 }
360
361 // If we don't need any custom attributes, use the base class behavior
362 if( !needsUrlEditor && !needsVariantHighlight && !needsTextVarRenderer )
363 return WX_GRID_TABLE_BASE::GetAttr( aRow, aCol, aKind );
364
365 // URL cells: use m_urlEditor as base, potentially with variant highlight overlay
366 if( needsUrlEditor )
367 {
368 if( needsVariantHighlight )
369 {
370 // Clone the URL editor attribute and add highlight color
371 attr = m_urlEditor->Clone();
372 attr->SetBackgroundColour( highlightColor );
373 }
374 else
375 {
376 // Just use the URL editor attribute directly
377 m_urlEditor->IncRef();
378 attr = m_urlEditor;
379 }
380
381 return enhanceAttr( attr, aRow, aCol, aKind );
382 }
383
384 // Non-URL cells: start with column attributes if they exist.
385 // This preserves checkbox renderers and other column-specific settings.
386 if( m_colAttrs.find( aCol ) != m_colAttrs.end() && m_colAttrs[aCol] )
387 {
388 attr = m_colAttrs[aCol]->Clone();
389 }
390 else
391 {
392 attr = new wxGridCellAttr();
393 }
394
395 if( needsVariantHighlight )
396 attr->SetBackgroundColour( highlightColor );
397
398 if( needsTextVarRenderer )
399 {
400 if( !m_textVarRenderer )
402
403 m_textVarRenderer->IncRef();
404 attr->SetRenderer( m_textVarRenderer );
405
406 // Tint text-var cells if not already highlighted by variant
407 if( !needsVariantHighlight )
408 {
409 wxColour bg = wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW );
410 bool isDark = ( bg.Red() + bg.Green() + bg.Blue() ) < 384;
411
412 attr->SetBackgroundColour( isDark ? wxColour( 80, 70, 30 ) // Dark amber
413 : wxColour( 255, 252, 200 ) ); // Light yellow
414 }
415 }
416
417 return enhanceAttr( attr, aRow, aCol, aKind );
418}
419
420
422 const wxString& refDelimiter,
423 const wxString& refRangeDelimiter,
424 bool resolveVars,
425 bool listMixedValues )
426{
427 std::vector<SCH_REFERENCE> references;
428 std::set<wxString> mixedValues;
429 wxString fieldValue;
430
431 for( const SCH_REFERENCE& ref : group.m_Refs )
432 {
433 if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
434 {
435 references.push_back( ref );
436 }
437 else // Other columns are either a single value or ROW_MULTI_ITEMS
438 {
439 KIID_PATH symbolKey = makeDataStoreKey( ref.GetSheetPath(), *ref.GetSymbol() );
440
441 if( !m_dataStore.contains( symbolKey ) || !m_dataStore[symbolKey].contains( m_cols[aCol].m_fieldName ) )
442 return INDETERMINATE_STATE;
443
444 wxString refFieldValue = m_dataStore[symbolKey][m_cols[aCol].m_fieldName];
445
446 // Show the effective state when a sheet forces it on, but do not change
447 // the stored value so the symbol is never stamped on apply.
448 if( ColIsAttribute( aCol ) && attributeInheritedFromSheet( ref, m_cols[aCol].m_fieldName ) )
449 refFieldValue = wxS( "1" );
450
451 if( resolveVars )
452 {
453 if( IsGeneratedField( m_cols[aCol].m_fieldName ) )
454 {
455 // Generated fields (e.g. ${QUANTITY}) can't have un-applied values as they're
456 // read-only. Resolve them against the field.
457 refFieldValue = getFieldShownText( ref, m_cols[aCol].m_fieldName );
458 }
459 else if( refFieldValue.Contains( wxT( "${" ) ) )
460 {
461 // Resolve variables in the un-applied value using the parent symbol and instance
462 // data.
463 std::function<bool( wxString* )> symbolResolver = [&]( wxString* token ) -> bool
464 {
465 return ref.GetSymbol()->ResolveTextVar( &ref.GetSheetPath(), token, m_currentVariant );
466 };
467
468 refFieldValue = ExpandTextVars( refFieldValue, & symbolResolver );
469 }
470 }
471
472 if( listMixedValues )
473 mixedValues.insert( refFieldValue );
474 else if( &ref == &group.m_Refs.front() )
475 fieldValue = refFieldValue;
476 else if( fieldValue != refFieldValue )
477 return INDETERMINATE_STATE;
478 }
479 }
480
481 if( listMixedValues )
482 {
483 fieldValue = wxEmptyString;
484
485 for( const wxString& value : mixedValues )
486 {
487 if( value.IsEmpty() )
488 continue;
489 else if( fieldValue.IsEmpty() )
490 fieldValue = value;
491 else
492 fieldValue += "," + value;
493 }
494 }
495
496 if( ColIsReference( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
497 {
498 // Remove duplicates (other units of multi-unit parts)
499 std::sort( references.begin(), references.end(),
500 []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
501 {
502 wxString l_ref( l.GetRef() << l.GetRefNumber() );
503 wxString r_ref( r.GetRef() << r.GetRefNumber() );
504 return StrNumCmp( l_ref, r_ref, true ) < 0;
505 } );
506
507 auto logicalEnd = std::unique( references.begin(), references.end(),
508 []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool
509 {
510 // If unannotated then we can't tell what units belong together
511 // so we have to leave them all
512 if( l.GetRefNumber() == wxT( "?" ) )
513 return false;
514
515 wxString l_ref( l.GetRef() << l.GetRefNumber() );
516 wxString r_ref( r.GetRef() << r.GetRefNumber() );
517 return l_ref == r_ref;
518 } );
519
520 references.erase( logicalEnd, references.end() );
521 }
522
523 if( ColIsReference( aCol ) )
524 fieldValue = SCH_REFERENCE_LIST::Shorthand( references, refDelimiter, refRangeDelimiter );
525 else if( ColIsQuantity( aCol ) )
526 fieldValue = wxString::Format( wxT( "%d" ), (int) references.size() );
527 else if( ColIsItemNumber( aCol ) && group.m_Flag != CHILD_ITEM )
528 fieldValue = wxString::Format( wxT( "%d" ), group.m_ItemNumber );
529
530 return fieldValue;
531}
532
533
534void FIELDS_EDITOR_GRID_DATA_MODEL::SetValue( int aRow, int aCol, const wxString& aValue )
535{
536 wxCHECK_RET( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), wxS( "Invalid column number" ) );
537
538 // Can't modify references or generated fields (e.g. ${QUANTITY})
539 if( ColIsReference( aCol )
540 || ( IsGeneratedField( m_cols[aCol].m_fieldName ) && !ColIsAttribute( aCol ) ) )
541 {
542 return;
543 }
544
545 if( aValue == INDETERMINATE_STATE )
546 return;
547
548 DATA_MODEL_ROW& rowGroup = m_rows[aRow];
549
550 const SCH_SYMBOL* sharedSymbol = nullptr;
551 bool isSharedInstance = false;
552
553 for( const SCH_REFERENCE& ref : rowGroup.m_Refs )
554 {
555 // Check to see if the symbol associated with this row has more than one instance.
556 if( const SCH_SYMBOL* symbol = ref.GetSymbol() )
557 {
558 isSharedInstance = ref.GetSheetPath().IsSharedPath();
559 sharedSymbol = symbol;
560 }
561
562 KIID_PATH key = makeDataStoreKey( ref.GetSheetPath(), *ref.GetSymbol() );
563 m_dataStore[key][m_cols[aCol].m_fieldName] = aValue;
564 }
565
566 // Update all of the other instances for the shared symbol as required.
567 if( isSharedInstance
568 && ( ( rowGroup.m_Flag == GROUP_SINGLETON ) || ( rowGroup.m_Flag == CHILD_ITEM ) ) )
569 {
570 for( DATA_MODEL_ROW& row : m_rows )
571 {
572 if( row.m_ItemNumber == aRow + 1 )
573 continue;
574
575 for( const SCH_REFERENCE& ref : row.m_Refs )
576 {
577 if( ref.GetSymbol() != sharedSymbol )
578 continue;
579
580 KIID_PATH key = makeDataStoreKey( ref.GetSheetPath(), *ref.GetSymbol() );
581 m_dataStore[key][m_cols[aCol].m_fieldName] = aValue;
582 }
583 }
584 }
585
586 m_edited = true;
587}
588
589
591{
592 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
593 return m_cols[aCol].m_fieldName == GetCanonicalFieldName( FIELD_T::REFERENCE );
594}
595
596
598{
599 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
600 return m_cols[aCol].m_fieldName == GetCanonicalFieldName( FIELD_T::VALUE );
601}
602
603
605{
606 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
607 return m_cols[aCol].m_fieldName == QUANTITY_VARIABLE;
608}
609
610
612{
613 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
614 return m_cols[aCol].m_fieldName == ITEM_NUMBER_VARIABLE;
615}
616
617
619{
620 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
621 return isAttribute( m_cols[aCol].m_fieldName );
622}
623
624
626 const DATA_MODEL_ROW& rhGroup,
627 FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol,
628 bool ascending )
629{
630 // Empty rows always go to the bottom, whether ascending or descending
631 if( lhGroup.m_Refs.size() == 0 )
632 return true;
633 else if( rhGroup.m_Refs.size() == 0 )
634 return false;
635
636 // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
637 // to get the opposite sort. i.e. ~(a<b) != (a>b)
638 auto local_cmp =
639 [ ascending ]( const auto a, const auto b )
640 {
641 if( ascending )
642 return a < b;
643 else
644 return a > b;
645 };
646
647 // Primary sort key is sortCol; secondary is always REFERENCE (column 0)
648 if( sortCol < 0 || sortCol >= dataModel->GetNumberCols() )
649 sortCol = 0;
650
651 wxString lhs = dataModel->GetValue( lhGroup, sortCol, wxT( ", " ), wxT( "-" ), true ).Trim( true ).Trim( false );
652 wxString rhs = dataModel->GetValue( rhGroup, sortCol, wxT( ", " ), wxT( "-" ), true ).Trim( true ).Trim( false );
653
654 if( lhs == rhs || dataModel->ColIsReference( sortCol ) )
655 {
656 wxString lhRef = lhGroup.m_Refs[0].GetRef() + lhGroup.m_Refs[0].GetRefNumber();
657 wxString rhRef = rhGroup.m_Refs[0].GetRef() + rhGroup.m_Refs[0].GetRefNumber();
658 return local_cmp( StrNumCmp( lhRef, rhRef, true ), 0 );
659 }
660 else
661 {
662 return local_cmp( ValueStringCompare( lhs, rhs ), 0 );
663 }
664}
665
666
668{
670
671 // We're going to sort the rows based on their first reference, so the first reference
672 // had better be the lowest one.
673 for( DATA_MODEL_ROW& row : m_rows )
674 {
675 std::sort( row.m_Refs.begin(), row.m_Refs.end(),
676 []( const SCH_REFERENCE& lhs, const SCH_REFERENCE& rhs )
677 {
678 wxString lhs_ref( lhs.GetRef() << lhs.GetRefNumber() );
679 wxString rhs_ref( rhs.GetRef() << rhs.GetRefNumber() );
680 return StrNumCmp( lhs_ref, rhs_ref, true ) < 0;
681 } );
682 }
683
684 std::sort( m_rows.begin(), m_rows.end(),
685 [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
686 {
687 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
688 } );
689
690 // Time to renumber the item numbers
691 int itemNumber = 1;
692
693 for( DATA_MODEL_ROW& row : m_rows )
694 {
695 row.m_ItemNumber = itemNumber++;
696 }
697
699}
700
701
703{
704 // If items are unannotated then we can't tell if they're units of the same symbol or not
705 if( lhRef.GetRefNumber() == wxT( "?" ) )
706 return false;
707
708 return ( lhRef.GetRef() == rhRef.GetRef() && lhRef.GetRefNumber() == rhRef.GetRefNumber() );
709}
710
711
713{
715 bool matchFound = false;
716
717 if( refCol == -1 )
718 return false;
719
720 // First check the reference column. This can be done directly out of the
721 // SCH_REFERENCEs as the references can't be edited in the grid.
722 if( m_cols[refCol].m_group )
723 {
724 // if we're grouping by reference, then only the prefix must match
725 if( lhRef.GetRef() != rhRef.GetRef() )
726 return false;
727
728 matchFound = true;
729 }
730
731 KIID_PATH lhRefKey = makeDataStoreKey( lhRef.GetSheetPath(), *lhRef.GetSymbol() );
732 KIID_PATH rhRefKey = makeDataStoreKey( rhRef.GetSheetPath(), *rhRef.GetSymbol() );
733
734 // Now check all the other columns.
735 for( size_t i = 0; i < m_cols.size(); ++i )
736 {
737 //Handled already
738 if( static_cast<int>( i ) == refCol )
739 continue;
740
741 if( !m_cols[i].m_group )
742 continue;
743
744 // If the field is generated (e.g. ${QUANTITY}), we need to resolve it through the symbol
745 // to get the actual current value; otherwise we need to pull it out of the store so the
746 // refresh can regroup based on values that haven't been applied to the schematic yet.
747 wxString lh, rh;
748
749 if( IsGeneratedField( m_cols[i].m_fieldName )
750 || IsGeneratedField( m_dataStore[lhRefKey][m_cols[i].m_fieldName] ) )
751 {
752 lh = getFieldShownText( lhRef, m_cols[i].m_fieldName );
753 }
754 else
755 {
756 lh = m_dataStore[lhRefKey][m_cols[i].m_fieldName];
757 }
758
759 if( IsGeneratedField( m_cols[i].m_fieldName )
760 || IsGeneratedField( m_dataStore[rhRefKey][m_cols[i].m_fieldName] ) )
761 {
762 rh = getFieldShownText( rhRef, m_cols[i].m_fieldName );
763 }
764 else
765 {
766 rh = m_dataStore[rhRefKey][m_cols[i].m_fieldName];
767 }
768
769 if( lh != rh )
770 return false;
771
772 matchFound = true;
773 }
774
775 return matchFound;
776}
777
778
780 const wxString& aFieldName )
781{
782 SCH_FIELD* field = aRef.GetSymbol()->GetField( aFieldName );
783
784 if( field )
785 {
786 if( field->IsPrivate() )
787 return wxEmptyString;
788 else
789 return field->GetShownText( &aRef.GetSheetPath(), false, 0, m_currentVariant );
790 }
791
792 // Handle generated fields with variables as names (e.g. ${QUANTITY}) that are not present in
793 // the symbol by giving them the correct value by resolving against the symbol
794 if( IsGeneratedField( aFieldName ) )
795 {
796 int depth = 0;
797 const SCH_SHEET_PATH& path = aRef.GetSheetPath();
798
799 std::function<bool( wxString* )> symbolResolver = [&]( wxString* token ) -> bool
800 {
801 return aRef.GetSymbol()->ResolveTextVar( &path, token, m_currentVariant, depth + 1 );
802 };
803
804 return ExpandTextVars( aFieldName, &symbolResolver );
805 }
806
807 return wxEmptyString;
808}
809
810
811bool FIELDS_EDITOR_GRID_DATA_MODEL::isAttribute( const wxString& aFieldName )
812{
813 return aFieldName == wxS( "${DNP}" ) || aFieldName == wxS( "${EXCLUDE_FROM_BOARD}" )
814 || aFieldName == wxS( "${EXCLUDE_FROM_BOM}" ) || aFieldName == wxS( "${EXCLUDE_FROM_POS_FILES}" )
815 || aFieldName == wxS( "${EXCLUDE_FROM_SIM}" );
816}
817
818
819wxString FIELDS_EDITOR_GRID_DATA_MODEL::getAttributeValue( const SCH_REFERENCE& aRef, const wxString& aAttributeName,
820 const wxString& aVariantName )
821{
822 if( aAttributeName == wxS( "${DNP}" ) )
823 return aRef.GetSymbolDNP( aVariantName ) ? wxS( "1" ) : wxS( "0" );
824
825 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
826 return aRef.GetSymbolExcludedFromBoard() ? wxS( "1" ) : wxS( "0" );
827
828 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
829 return aRef.GetSymbolExcludedFromBOM( aVariantName ) ? wxS( "1" ) : wxS( "0" );
830
831 if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
832 return aRef.GetSymbolExcludedFromSim( aVariantName ) ? wxS( "1" ) : wxS( "0" );
833
834 if( aAttributeName == wxS( "${EXCLUDE_FROM_POS_FILES}" ) )
835 return aRef.GetSymbolExcludedFromPosFiles( aVariantName ) ? wxS( "1" ) : wxS( "0" );
836
837 return wxS( "0" );
838}
839
840
842 const wxString& aAttributeName ) const
843{
844 const SCH_SHEET_PATH& path = aRef.GetSheetPath();
845
846 if( aAttributeName == wxS( "${DNP}" ) )
847 return path.GetDNP( m_currentVariant );
848 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
849 return path.GetExcludedFromBoard( m_currentVariant );
850 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
851 return path.GetExcludedFromBOM( m_currentVariant );
852 else if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
853 return path.GetExcludedFromSim( m_currentVariant );
854
855 return false;
856}
857
858
860{
861 if( !ColIsAttribute( aCol ) || aGroup.m_Refs.empty() )
862 return false;
863
864 // Lock the cell only when every symbol in the row inherits it, so a mixed group
865 // stays editable and shows the indeterminate state.
866 for( const SCH_REFERENCE& ref : aGroup.m_Refs )
867 {
868 if( !attributeInheritedFromSheet( ref, m_cols[aCol].m_fieldName ) )
869 return false;
870 }
871
872 return true;
873}
874
875
877 const wxString& aFieldName )
878{
879 const SCH_SYMBOL* symbol = aRef.GetSymbol();
880
881 if( !symbol )
882 return wxEmptyString;
883
884 // For attributes, get the default (non-variant) value
885 if( isAttribute( aFieldName ) )
886 return getAttributeValue( aRef, aFieldName, wxEmptyString );
887
888 // For regular fields, get the text without variant override
889 if( const SCH_FIELD* field = symbol->GetField( aFieldName ) )
890 {
891 if( field->IsPrivate() )
892 return wxEmptyString;
893
894 // Get the field text with empty variant name (default value)
895 wxString value = symbol->Schematic()->ConvertKIIDsToRefs(
896 field->GetText( &aRef.GetSheetPath(), wxEmptyString ) );
897 return value;
898 }
899
900 // For generated fields, return the field name itself
901 if( IsGeneratedField( aFieldName ) )
902 return aFieldName;
903
904 return wxEmptyString;
905}
906
907
909 const wxString& aAttributeName,
910 const wxString& aValue,
911 const wxString& aVariantName )
912{
913 bool attrChanged = false;
914 bool newValue = aValue == wxS( "1" );
915
916 if( aAttributeName == wxS( "${DNP}" ) )
917 {
918 attrChanged = aRef.GetSymbolDNP( aVariantName ) != newValue;
919
920 if( attrChanged )
921 aRef.SetSymbolDNP( newValue, aVariantName );
922 }
923 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
924 {
925 attrChanged = aRef.GetSymbolExcludedFromBoard() != newValue;
926
927 if( attrChanged )
928 aRef.SetSymbolExcludedFromBoard( newValue );
929 }
930 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
931 {
932 attrChanged = aRef.GetSymbolExcludedFromBOM( aVariantName ) != newValue;
933
934 if( attrChanged )
935 aRef.SetSymbolExcludedFromBOM( newValue, aVariantName );
936 }
937 else if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
938 {
939 attrChanged = aRef.GetSymbolExcludedFromSim( aVariantName ) != newValue;
940
941 if( attrChanged )
942 aRef.SetSymbolExcludedFromSim( newValue, aVariantName );
943 }
944 else if( aAttributeName == wxS( "${EXCLUDE_FROM_POS_FILES}" ) )
945 {
946 attrChanged = aRef.GetSymbolExcludedFromPosFiles( aVariantName ) != newValue;
947
948 if( attrChanged )
949 aRef.SetSymbolExcludedFromPosFiles( newValue, aVariantName );
950 }
951
952 return attrChanged;
953}
954
955
960
961
966
967
969{
970 if( !m_rebuildsEnabled )
971 return;
972
973 if( GetView() )
974 {
975 // Commit any pending in-place edits before the row gets moved out from under
976 // the editor.
977 static_cast<WX_GRID*>( GetView() )->CommitPendingChanges( true );
978
979 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() );
980 GetView()->ProcessTableMessage( msg );
981 }
982
983 m_rows.clear();
984
985 EDA_COMBINED_MATCHER matcher( m_filter.Lower(), CTX_SEARCH );
986
987 for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
988 {
990
991 if( !m_filter.IsEmpty() && !matcher.Find( ref.GetFullRef().Lower() ) )
992 continue;
993
994 if( m_excludeDNP )
995 {
996 bool isDNP = false;
997
998 if( !m_variantNames.empty() )
999 {
1000 for( const wxString& variantName : m_variantNames )
1001 {
1002 if( ref.GetSymbol()->ResolveDNP( &ref.GetSheetPath(), variantName )
1003 || ref.GetSheetPath().GetDNP( variantName ) )
1004 {
1005 isDNP = true;
1006 break;
1007 }
1008 }
1009 }
1010 else
1011 {
1012 isDNP = ref.GetSymbol()->ResolveDNP( &ref.GetSheetPath(), m_currentVariant )
1014 }
1015
1016 if( isDNP )
1017 continue;
1018 }
1019
1020 if( !m_includeExcluded )
1021 {
1022 bool isExcluded = false;
1023
1024 if( !m_variantNames.empty() )
1025 {
1026 for( const wxString& variantName : m_variantNames )
1027 {
1028 if( ref.GetSymbol()->ResolveExcludedFromBOM( &ref.GetSheetPath(), variantName )
1029 || ref.GetSheetPath().GetExcludedFromBOM( variantName ) )
1030 {
1031 isExcluded = true;
1032 break;
1033 }
1034 }
1035 }
1036 else
1037 {
1038 isExcluded = ref.GetSymbol()->ResolveExcludedFromBOM( &ref.GetSheetPath(), m_currentVariant )
1040 }
1041
1042 if( isExcluded )
1043 continue;
1044 }
1045
1046 // Check if the symbol if on the current sheet or, in the sheet path somewhere
1047 // depending on scope
1048 if( ( m_scope == SCOPE::SCOPE_SHEET && ref.GetSheetPath() != m_path )
1049 || ( m_scope == SCOPE::SCOPE_SHEET_RECURSIVE
1050 && !ref.GetSheetPath().IsContainedWithin( m_path ) ) )
1051 {
1052 continue;
1053 }
1054
1055 bool matchFound = false;
1056
1057 // Performance optimization for ungrouped case to skip the N^2 for loop
1058 if( !m_groupingEnabled && !ref.IsMultiUnit() )
1059 {
1060 m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
1061 continue;
1062 }
1063
1064 // See if we already have a row which this symbol fits into
1065 for( DATA_MODEL_ROW& row : m_rows )
1066 {
1067 // all group members must have identical refs so just use the first one
1068 SCH_REFERENCE rowRef = row.m_Refs[0];
1069
1070 if( unitMatch( ref, rowRef ) )
1071 {
1072 matchFound = true;
1073 row.m_Refs.push_back( ref );
1074 break;
1075 }
1076 else if( m_groupingEnabled && groupMatch( ref, rowRef ) )
1077 {
1078 matchFound = true;
1079 row.m_Refs.push_back( ref );
1080 row.m_Flag = GROUP_COLLAPSED;
1081 break;
1082 }
1083 }
1084
1085 if( !matchFound )
1086 m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
1087 }
1088
1089 if( GetView() )
1090 {
1091 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() );
1092 GetView()->ProcessTableMessage( msg );
1093 }
1094
1095 Sort();
1096}
1097
1098
1100{
1101 std::vector<DATA_MODEL_ROW> children;
1102
1103 for( SCH_REFERENCE& ref : m_rows[aRow].m_Refs )
1104 {
1105 bool matchFound = false;
1106
1107 // See if we already have a child group which this symbol fits into
1108 for( DATA_MODEL_ROW& child : children )
1109 {
1110 // group members are by definition all matching, so just check
1111 // against the first member
1112 if( unitMatch( ref, child.m_Refs[0] ) )
1113 {
1114 matchFound = true;
1115 child.m_Refs.push_back( ref );
1116 break;
1117 }
1118 }
1119
1120 if( !matchFound )
1121 children.emplace_back( DATA_MODEL_ROW( ref, CHILD_ITEM ) );
1122 }
1123
1124 if( children.size() < 2 )
1125 return;
1126
1127 std::sort( children.begin(), children.end(),
1128 [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool
1129 {
1130 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
1131 } );
1132
1133 m_rows[aRow].m_Flag = GROUP_EXPANDED;
1134 m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() );
1135
1136 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, children.size() );
1137 GetView()->ProcessTableMessage( msg );
1138}
1139
1140
1142{
1143 auto firstChild = m_rows.begin() + aRow + 1;
1144 auto afterLastChild = firstChild;
1145 int deleted = 0;
1146
1147 while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM )
1148 {
1149 deleted++;
1150 afterLastChild++;
1151 }
1152
1153 m_rows[aRow].m_Flag = GROUP_COLLAPSED;
1154 m_rows.erase( firstChild, afterLastChild );
1155
1156 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted );
1157 GetView()->ProcessTableMessage( msg );
1158}
1159
1160
1162{
1163 DATA_MODEL_ROW& group = m_rows[aRow];
1164
1165 if( group.m_Flag == GROUP_COLLAPSED )
1166 ExpandRow( aRow );
1167 else if( group.m_Flag == GROUP_EXPANDED )
1168 CollapseRow( aRow );
1169}
1170
1171
1173{
1174 for( size_t i = 0; i < m_rows.size(); ++i )
1175 {
1176 if( m_rows[i].m_Flag == GROUP_EXPANDED )
1177 {
1178 CollapseRow( i );
1180 }
1181 }
1182}
1183
1184
1186{
1187 for( size_t i = 0; i < m_rows.size(); ++i )
1188 {
1189 if( m_rows[i].m_Flag == GROUP_COLLAPSED_DURING_SORT )
1190 ExpandRow( i );
1191 }
1192}
1193
1194
1196 const wxString& aVariantName )
1197{
1198 bool symbolModified = false;
1199 std::unique_ptr<SCH_SYMBOL> symbolCopy;
1200
1201 for( size_t i = 0; i < m_symbolsList.GetCount(); i++ )
1202 {
1203 SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol();
1204 SCH_SYMBOL* nextSymbol = nullptr;
1205
1206 if( ( i + 1 ) < m_symbolsList.GetCount() )
1207 nextSymbol = m_symbolsList[i + 1].GetSymbol();
1208
1209 if( i == 0 )
1210 symbolCopy = std::make_unique<SCH_SYMBOL>( *symbol );
1211
1212 KIID_PATH key = makeDataStoreKey( m_symbolsList[i].GetSheetPath(), *symbol );
1213 const std::map<wxString, wxString>& fieldStore = m_dataStore[key];
1214
1215 for( const auto& [srcName, srcValue] : fieldStore )
1216 {
1217 // Attributes bypass the field logic, so handle them first
1218 if( isAttribute( srcName ) )
1219 {
1220 symbolModified |= setAttributeValue( m_symbolsList[i], srcName, srcValue, aVariantName );
1221 continue;
1222 }
1223
1224 // Skip generated fields with variables as names (e.g. ${QUANTITY});
1225 // they can't be edited
1226 if( IsGeneratedField( srcName ) )
1227 continue;
1228
1229 SCH_FIELD* destField = symbol->GetField( srcName );
1230
1231 if( destField && destField->IsPrivate() )
1232 {
1233 if( srcValue.IsEmpty() )
1234 continue;
1235 else
1236 destField->SetPrivate( false );
1237 }
1238
1239 int col = GetFieldNameCol( srcName );
1240 bool userAdded = ( col != -1 && m_cols[col].m_userAdded );
1241
1242 // Add a not existing field if it has a value for this symbol
1243 bool createField = !destField && ( !srcValue.IsEmpty() || userAdded );
1244
1245 if( createField )
1246 {
1247 destField = symbol->AddField( SCH_FIELD( symbol, FIELD_T::USER, srcName ) );
1248 destField->SetTextAngle( symbol->GetField( FIELD_T::REFERENCE )->GetTextAngle() );
1249
1250 if( const TEMPLATE_FIELDNAME* srcTemplate = aTemplateFieldnames.GetFieldName( srcName ) )
1251 destField->SetVisible( srcTemplate->m_Visible );
1252 else
1253 destField->SetVisible( false );
1254
1255 destField->SetTextPos( symbol->GetPosition() );
1256 symbolModified = true;
1257 }
1258
1259 if( !destField )
1260 continue;
1261
1262 // Reference is not editable from this dialog
1263 if( destField->GetId() == FIELD_T::REFERENCE )
1264 continue;
1265
1266 wxString previousValue = destField->GetText( &m_symbolsList[i].GetSheetPath(), aVariantName );
1267
1268 destField->SetText( symbol->Schematic()->ConvertRefsToKIIDs( srcValue ), &m_symbolsList[i].GetSheetPath(),
1269 aVariantName );
1270
1271 if( !createField && ( previousValue != srcValue ) )
1272 symbolModified = true;
1273 }
1274
1275 for( int ii = static_cast<int>( symbol->GetFields().size() ) - 1; ii >= 0; ii-- )
1276 {
1277 if( symbol->GetFields()[ii].IsMandatory() || symbol->GetFields()[ii].IsPrivate() )
1278 continue;
1279
1280 const wxString& existingName = symbol->GetFields()[ii].GetName();
1281
1282 bool stillTracked = std::any_of( fieldStore.begin(), fieldStore.end(),
1283 [&]( const auto& kv )
1284 {
1285 return kv.first == existingName;
1286 } );
1287
1288 if( !stillTracked )
1289 {
1290 symbol->GetFields().erase( symbol->GetFields().begin() + ii );
1291 symbolModified = true;
1292 }
1293 }
1294
1295 if( symbolModified && ( symbol != nextSymbol ) )
1296 aCommit.Modified( symbol, symbolCopy.release(), m_symbolsList[i].GetSheetPath().LastScreen() );
1297
1298 // Only reset the modified flag and next symbol copy if the next symbol is different from the current one.
1299 if( symbol != nextSymbol )
1300 {
1301 if( nextSymbol )
1302 symbolCopy = std::make_unique<SCH_SYMBOL>( *nextSymbol );
1303 else
1304 symbolCopy.reset( nullptr );
1305
1306 symbolModified = false;
1307 }
1308 }
1309
1310 m_edited = false;
1311}
1312
1313
1315{
1316 int width = 0;
1317
1318 if( ColIsReference( aCol ) )
1319 {
1320 for( int row = 0; row < GetNumberRows(); ++row )
1321 width = std::max( width, KIUI::GetTextSize( GetValue( row, aCol ), GetView() ).x );
1322 }
1323 else
1324 {
1325 wxString fieldName = GetColFieldName( aCol ); // symbol fieldName or Qty string
1326
1327 for( unsigned symbolRef = 0; symbolRef < m_symbolsList.GetCount(); ++symbolRef )
1328 {
1329 KIID_PATH key = makeDataStoreKey( m_symbolsList[symbolRef].GetSheetPath(),
1330 *m_symbolsList[symbolRef].GetSymbol() );
1331 wxString text = m_dataStore[key][fieldName];
1332
1333 width = std::max( width, KIUI::GetTextSize( text, GetView() ).x );
1334 }
1335 }
1336
1337 return width;
1338}
1339
1340
1342{
1343 // Hide and un-group everything by default
1344 for( size_t i = 0; i < m_cols.size(); i++ )
1345 {
1346 SetShowColumn( i, false );
1347 SetGroupColumn( i, false );
1348 }
1349
1350 std::set<wxString> seen;
1351 std::vector<wxString> order;
1352
1353 // Set columns that are present and shown
1354 for( const BOM_FIELD& field : aPreset.fieldsOrdered )
1355 {
1356 // Ignore empty fields
1357 if( !field.name || seen.count( field.name ) )
1358 continue;
1359
1360 seen.insert( field.name );
1361 order.emplace_back( field.name );
1362
1363 int col = GetFieldNameCol( field.name );
1364
1365 // Add any missing fields, if the user doesn't add any data
1366 // they won't be saved to the symbols anyway
1367 if( col == -1 )
1368 {
1369 AddColumn( field.name, field.label, true );
1370 col = GetFieldNameCol( field.name );
1371 }
1372 else
1373 {
1374 SetColLabelValue( col, field.label );
1375 }
1376
1377 SetGroupColumn( col, field.groupBy );
1378 SetShowColumn( col, field.show );
1379 }
1380
1381 // Set grouping columns
1383
1384 SetFieldsOrder( order );
1385
1386 // Set our sorting
1387 int sortCol = GetFieldNameCol( aPreset.sortField );
1388
1389 if( sortCol == -1 )
1391
1392 SetSorting( sortCol, aPreset.sortAsc );
1393
1394 SetFilter( aPreset.filterString );
1395 SetExcludeDNP( aPreset.excludeDNP );
1397
1398 RebuildRows();
1399}
1400
1401
1403{
1404 BOM_PRESET current;
1405 current.readOnly = false;
1406 current.fieldsOrdered = GetFieldsOrdered();
1407
1408 if( GetSortCol() >= 0 && GetSortCol() < GetNumberCols() )
1409 current.sortField = GetColFieldName( GetSortCol() );
1410
1411 current.sortAsc = GetSortAsc();
1412 current.filterString = GetFilter();
1413 current.groupSymbols = GetGroupingEnabled();
1414 current.excludeDNP = GetExcludeDNP();
1416
1417 return current;
1418}
1419
1420
1422{
1423 wxString out;
1424
1425 if( m_cols.empty() )
1426 return out;
1427
1428 int last_col = -1;
1429
1430 // Find the location for the line terminator
1431 for( size_t col = 0; col < m_cols.size(); col++ )
1432 {
1433 if( m_cols[col].m_show )
1434 last_col = static_cast<int>( col );
1435 }
1436
1437 // No shown columns
1438 if( last_col == -1 )
1439 return out;
1440
1441 if( settings.includeByteOrderMark )
1442 out.Append( wxString::FromUTF8( "\xEF\xBB\xBF" ) );
1443
1444 auto formatField =
1445 [&]( wxString field, bool last ) -> wxString
1446 {
1447 if( !settings.keepLineBreaks )
1448 {
1449 field.Replace( wxS( "\r" ), wxS( "" ) );
1450 field.Replace( wxS( "\n" ), wxS( "" ) );
1451 }
1452
1453 if( !settings.keepTabs )
1454 {
1455 field.Replace( wxS( "\t" ), wxS( "" ) );
1456 }
1457
1458 if( !settings.stringDelimiter.IsEmpty() )
1459 {
1460 field.Replace( settings.stringDelimiter,
1461 settings.stringDelimiter + settings.stringDelimiter );
1462 }
1463
1464 return settings.stringDelimiter + field + settings.stringDelimiter
1465 + ( last ? wxString( wxS( "\n" ) ) : settings.fieldDelimiter );
1466 };
1467
1468 // Column names
1469 for( size_t col = 0; col < m_cols.size(); col++ )
1470 {
1471 if( !m_cols[col].m_show )
1472 continue;
1473
1474 out.Append( formatField( m_cols[col].m_label, col == static_cast<size_t>( last_col ) ) );
1475 }
1476
1477 // Data rows
1478 for( size_t row = 0; row < m_rows.size(); row++ )
1479 {
1480 // Don't output child rows
1481 if( GetRowFlags( static_cast<int>( row ) ) == CHILD_ITEM )
1482 continue;
1483
1484 for( size_t col = 0; col < m_cols.size(); col++ )
1485 {
1486 if( !m_cols[col].m_show )
1487 continue;
1488
1489 // Get the unannotated version of the field, e.g. no "> " or "v " by
1490 out.Append( formatField( GetExportValue( static_cast<int>( row ), static_cast<int>( col ),
1491 settings.refDelimiter, settings.refRangeDelimiter ),
1492 col == static_cast<size_t>( last_col ) ) );
1493 }
1494 }
1495
1496 return out;
1497}
1498
1499
1501{
1502 bool refListChanged = false;
1503
1504 for( const SCH_REFERENCE& ref : aRefs )
1505 {
1506 if( !m_symbolsList.Contains( ref ) )
1507 {
1508 SCH_SYMBOL* symbol = ref.GetSymbol();
1509
1510 m_symbolsList.AddItem( ref );
1511
1512 KIID_PATH key = makeDataStoreKey( ref.GetSheetPath(), *symbol );
1513
1514 // Update the fields of every reference
1515 for( const SCH_FIELD& field : symbol->GetFields() )
1516 {
1517 if( !field.IsPrivate() )
1518 {
1519 wxString name = field.GetCanonicalName();
1520 wxString value = symbol->Schematic()->ConvertKIIDsToRefs( field.GetText() );
1521
1522 m_dataStore[key][name] = value;
1523 }
1524 }
1525
1526 for( const DATA_MODEL_COL& col : m_cols )
1527 m_dataStore[key].try_emplace( col.m_fieldName, wxEmptyString );
1528
1529 refListChanged = true;
1530 }
1531 }
1532
1533 if( refListChanged )
1534 m_symbolsList.SortBySymbolPtr();
1535}
1536
1537
1539{
1540 // The schematic event listener passes us the symbol after it has been removed,
1541 // so we can't just work with a SCH_REFERENCE_LIST like the other handlers as the
1542 // references are already gone. Instead we need to prune our list.
1543
1544 // Since we now use full KIID_PATH as keys, we need to find and remove all entries
1545 // that correspond to this symbol (their keys end with the symbol's UUID)
1546 KIID symbolUuid = aSymbol.m_Uuid;
1547 std::vector<KIID_PATH> keysToRemove;
1548
1549 for( const auto& [key, value] : m_dataStore )
1550 {
1551 if( !key.empty() && ( key.back() == symbolUuid ) )
1552 keysToRemove.push_back( key );
1553 }
1554
1555 for( const KIID_PATH& key : keysToRemove )
1556 m_dataStore.erase( key );
1557
1558 // Remove all refs that match this symbol using remove_if
1559 m_symbolsList.erase( std::remove_if( m_symbolsList.begin(), m_symbolsList.end(),
1560 [&aSymbol]( const SCH_REFERENCE& ref ) -> bool
1561 {
1562 return ref.GetSymbol()->m_Uuid == aSymbol.m_Uuid;
1563 } ),
1564 m_symbolsList.end() );
1565}
1566
1567
1569{
1570 for( const SCH_REFERENCE& ref : aRefs )
1571 {
1572 int index = m_symbolsList.FindRefByFullPath( ref.GetFullPath() );
1573
1574 if( index != -1 )
1575 {
1576 KIID_PATH key = makeDataStoreKey( ref.GetSheetPath(), *ref.GetSymbol() );
1577 m_dataStore.erase( key );
1578 m_symbolsList.RemoveItem( index );
1579 }
1580 }
1581}
1582
1583
1585{
1586 bool refListChanged = false;
1587
1588 for( const SCH_REFERENCE& ref : aRefs )
1589 {
1590 // Update the fields of every reference. Do this by iterating through the data model
1591 // columns; we must have all fields in the symbol added to the data model at this point,
1592 // and some of the data model columns may be variables that are not present in the symbol
1593 for( const DATA_MODEL_COL& col : m_cols )
1594 updateDataStoreSymbolField( ref, col.m_fieldName );
1595
1596 if( SCH_REFERENCE* listRef = m_symbolsList.FindItem( ref ) )
1597 {
1598 *listRef = ref;
1599 }
1600 else
1601 {
1602 m_symbolsList.AddItem( ref );
1603 refListChanged = true;
1604 }
1605 }
1606
1607 if( refListChanged )
1608 m_symbolsList.SortBySymbolPtr();
1609}
1610
1611
1613{
1614 // Serialize the un-applied edit store keyed by symbol identity (sheet path + UUID), so that
1615 // restoring it is independent of the current row grouping/order.
1616 nlohmann::json j = nlohmann::json::object();
1617
1618 for( const auto& [key, fields] : m_dataStore )
1619 {
1620 nlohmann::json jfields = nlohmann::json::object();
1621
1622 for( const auto& [name, value] : fields )
1623 jfields[std::string( name.ToUTF8() )] = std::string( value.ToUTF8() );
1624
1625 j[std::string( key.AsString().ToUTF8() )] = jfields;
1626 }
1627
1628 return wxString( j.dump() );
1629}
1630
1631
1633{
1634 nlohmann::json j = nlohmann::json::parse( aState.ToStdString(), nullptr, false );
1635
1636 if( !j.is_object() )
1637 return;
1638
1639 for( auto it = j.begin(); it != j.end(); ++it )
1640 {
1641 KIID_PATH key( wxString::FromUTF8( it.key().c_str() ) );
1642 std::map<wxString, wxString>& fields = m_dataStore[key];
1643
1644 for( auto fit = it.value().begin(); fit != it.value().end(); ++fit )
1645 fields[wxString::FromUTF8( fit.key().c_str() )] =
1646 wxString::FromUTF8( fit.value().get<std::string>().c_str() );
1647 }
1648
1649 m_edited = true;
1650 RebuildRows();
1651
1652 if( GetView() )
1653 GetView()->ForceRefresh();
1654}
1655
1656
1657bool FIELDS_EDITOR_GRID_DATA_MODEL::DeleteRows( size_t aPosition, size_t aNumRows )
1658{
1659 size_t curNumRows = m_rows.size();
1660
1661 if( aPosition >= curNumRows )
1662 {
1663 wxFAIL_MSG( wxString::Format( wxT( "Called FIELDS_EDITOR_GRID_DATA_MODEL::DeleteRows(aPosition=%lu, "
1664 "aNumRows=%lu)\nPosition value is invalid for present table with %lu rows" ),
1665 (unsigned long) aPosition, (unsigned long) aNumRows,
1666 (unsigned long) curNumRows ) );
1667
1668 return false;
1669 }
1670
1671 if( aNumRows > curNumRows - aPosition )
1672 {
1673 aNumRows = curNumRows - aPosition;
1674 }
1675
1676 if( aNumRows >= curNumRows )
1677 {
1678 m_rows.clear();
1679 m_dataStore.clear();
1680 }
1681 else
1682 {
1683 const auto first = m_rows.begin() + aPosition;
1684 std::vector<SCH_REFERENCE> dataMapRefs = first->m_Refs;
1685 m_rows.erase( first, first + aNumRows );
1686
1687 for( const SCH_REFERENCE& ref : dataMapRefs )
1688 m_dataStore.erase( ref.GetSheetPath().Path() );
1689 }
1690
1691 if( GetView() )
1692 {
1693 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aPosition, aNumRows );
1694 GetView()->ProcessTableMessage( msg );
1695 }
1696
1697 return true;
1698}
1699
1700
1701std::vector<FIELD_CASE_CONFLICT> DetectFieldCaseConflicts( const SCH_REFERENCE_LIST& aSymbols )
1702{
1703 std::vector<FIELD_CASE_CONFLICT> conflicts;
1704
1705 for( unsigned i = 0; i < aSymbols.GetCount(); ++i )
1706 {
1707 SCH_SYMBOL* symbol = aSymbols[i].GetSymbol();
1708
1709 if( !symbol )
1710 continue;
1711
1712 std::map<wxString, std::vector<std::pair<wxString, wxString>>> groups;
1713
1714 for( const SCH_FIELD& field : symbol->GetFields() )
1715 {
1716 if( field.IsMandatory() || field.IsPrivate() )
1717 continue;
1718
1719 groups[field.GetName().Lower()].emplace_back( field.GetName(), field.GetText() );
1720 }
1721
1722 for( const auto& [key, members] : groups )
1723 {
1724 if( members.size() < 2 )
1725 continue;
1726
1728 c.symbol = symbol;
1729 c.sheetPath = aSymbols[i].GetSheetPath();
1730 c.reference = symbol->GetRef( &c.sheetPath );
1731 c.caseFoldedKey = key;
1732 c.variants = members;
1733 conflicts.push_back( std::move( c ) );
1734 }
1735 }
1736
1737 return conflicts;
1738}
int index
const char * name
COMMIT & Modified(EDA_ITEM *aItem, EDA_ITEM *aCopy, BASE_SCREEN *aScreen=nullptr)
Create an undo entry for an item that has been already modified.
Definition commit.cpp:180
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:531
virtual void SetTextPos(const VECTOR2I &aPoint)
Definition eda_text.cpp:576
virtual void SetVisible(bool aVisible)
Definition eda_text.cpp:381
virtual EDA_ANGLE GetTextAngle() const
Definition eda_text.h:168
virtual void SetTextAngle(const EDA_ANGLE &aAngle)
Definition eda_text.cpp:294
int GetFieldNameCol(const wxString &aFieldName) const
std::vector< DATA_MODEL_ROW > m_rows
void ApplyBomPreset(const BOM_PRESET &preset)
void SetFieldsOrder(const std::vector< wxString > &aNewOrder)
SCH_REFERENCE_LIST m_symbolsList
The flattened by hierarchy list of symbols.
wxGridCellRenderer * m_textVarRenderer
Renderer for cells with text variable references.
wxString m_currentVariant
Current variant name for highlighting.
bool DeleteRows(size_t aPosition=0, size_t aNumRows=1) override
bool groupMatch(const SCH_REFERENCE &lhRef, const SCH_REFERENCE &rhRef)
bool unitMatch(const SCH_REFERENCE &lhRef, const SCH_REFERENCE &rhRef)
wxString SerializeUndoState() const override
wxString getAttributeValue(const SCH_REFERENCE &aRef, const wxString &aAttributeName, const wxString &aVariantNames)
wxString GetExportValue(int aRow, int aCol, const wxString &refDelimiter, const wxString &refRangeDelimiter)
wxString getDefaultFieldValue(const SCH_REFERENCE &aRef, const wxString &aFieldName)
Get the default (non-variant) value for a field.
wxString getFieldShownText(const SCH_REFERENCE &aRef, const wxString &aFieldName)
wxString GetResolvedValue(int aRow, int aCol)
void RenameColumn(int aCol, const wxString &newName)
FIELDS_EDITOR_GRID_DATA_MODEL(const SCH_REFERENCE_LIST &aSymbolsList, wxGridCellAttr *aURLEditor)
bool IsExpanderColumn(int aCol) const override
wxString Export(const BOM_FMT_PRESET &settings)
void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser)
std::vector< wxString > m_variantNames
Variant names for multi-variant DNP filtering.
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)
bool setAttributeValue(SCH_REFERENCE &aRef, const wxString &aAttributeName, const wxString &aValue, const wxString &aVariantName=wxEmptyString)
Set the attribute value.
void RestoreUndoState(const wxString &aState) override
bool attributeInheritedFromSheet(const SCH_REFERENCE &aRef, const wxString &aAttributeName) const
void updateDataStoreSymbolField(const SCH_REFERENCE &aSymbolRef, const wxString &aFieldName)
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
bool rowAttributeInheritedFromSheet(const DATA_MODEL_ROW &aGroup, int aCol)
void UpdateReferences(const SCH_REFERENCE_LIST &aRefs)
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
std::map< KIID_PATH, std::map< wxString, wxString > > m_dataStore
void ApplyData(SCH_COMMIT &aCommit, TEMPLATES &aTemplateFieldnames, const wxString &aVariantName)
void SetColLabelValue(int aCol, const wxString &aLabel) override
void RemoveReferences(const SCH_REFERENCE_LIST &aRefs)
void SetShowColumn(int aCol, bool show)
void AddReferences(const SCH_REFERENCE_LIST &aRefs)
Cell renderer that shows the expanded result of text variables (e.g.
wxSize GetBestSize(wxGrid &aGrid, wxGridCellAttr &aAttr, wxDC &aDC, int aRow, int aCol) override
wxGridCellRenderer * Clone() const override
void Draw(wxGrid &aGrid, wxGridCellAttr &aAttr, wxDC &aDC, const wxRect &aRect, int aRow, int aCol, bool isSelected) override
Definition kiid.h:46
wxString ConvertKIIDsToRefs(const wxString &aSource) const
wxString ConvertRefsToKIIDs(const wxString &aSource) const
virtual const wxString & GetText() const override
Return the string associated with the text object.
Definition sch_field.h:128
FIELD_T GetId() const
Definition sch_field.h:132
wxString GetShownText(const SCH_SHEET_PATH *aPath, bool aAllowExtraText, int aDepth=0, const wxString &aVariantName=wxEmptyString) const
void SetText(const wxString &aText) override
void SetPrivate(bool aPrivate)
Definition sch_item.h:247
SCHEMATIC * Schematic() const
Search the item hierarchy to find a SCHEMATIC.
Definition sch_item.cpp:268
bool IsPrivate() const
Definition sch_item.h:248
bool ResolveExcludedFromBOM(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const
Definition sch_item.cpp:314
bool ResolveDNP(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const
Definition sch_item.cpp:362
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
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.
A helper to define a symbol's reference designator in a schematic.
void SetSymbolExcludedFromBoard(bool aEnable)
const SCH_SHEET_PATH & GetSheetPath() const
void SetSymbolExcludedFromBOM(bool aEnable, const wxString &aVariant=wxEmptyString)
bool GetSymbolExcludedFromBOM(const wxString &aVariant=wxEmptyString) const
bool GetSymbolDNP(const wxString &aVariant=wxEmptyString) const
SCH_SYMBOL * GetSymbol() const
bool GetSymbolExcludedFromPosFiles(const wxString &aVariant=wxEmptyString) const
void SetSymbolExcludedFromSim(bool aEnable, const wxString &aVariant=wxEmptyString)
wxString GetRef() const
bool GetSymbolExcludedFromBoard() const
wxString GetFullRef(bool aIncludeUnit=true) const
Return reference name with unit altogether.
bool GetSymbolExcludedFromSim(const wxString &aVariant=wxEmptyString) const
void SetSymbolExcludedFromPosFiles(bool aEnable, const wxString &aVariant=wxEmptyString)
bool IsMultiUnit() const
wxString GetRefNumber() const
void SetSymbolDNP(bool aEnable, const wxString &aVariant=wxEmptyString)
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
bool IsSharedPath() const
Determine if this sheet path is shared in a complex hierarchy.
bool GetExcludedFromBOM() const
KIID_PATH Path() const
Get the sheet path as an KIID_PATH.
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:69
void GetFields(std::vector< SCH_FIELD * > &aVector, bool aVisibleOnly) const override
Populate a std::vector with SCH_FIELDs, sorted in ordinal order.
VECTOR2I GetPosition() const override
Definition sch_symbol.h:914
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.
const wxString GetRef(const SCH_SHEET_PATH *aSheet, bool aIncludeUnit=false) const override
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
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:43
std::map< int, wxGridCellAttr * > m_colAttrs
Definition wx_grid.h:100
wxGridCellAttr * GetAttr(int aRow, int aCol, wxGridCellAttr::wxAttrKind aKind) override
Definition wx_grid.h:64
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:470
The common library.
@ CTX_SEARCH
std::vector< FIELD_CASE_CONFLICT > DetectFieldCaseConflicts(const SCH_REFERENCE_LIST &aSymbols)
static KIID_PATH makeDataStoreKey(const SCH_SHEET_PATH &aSheetPath, const SCH_SYMBOL &aSymbol)
Create a unique key for the data store by combining the KIID_PATH from the SCH_SHEET_PATH with the sy...
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
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
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
wxString name
wxString fieldDelimiter
bool includeByteOrderMark
wxString stringDelimiter
wxString refRangeDelimiter
wxString refDelimiter
wxString sortField
bool groupSymbols
std::vector< BOM_FIELD > fieldsOrdered
bool includeExcludedFromBOM
bool excludeDNP
wxString filterString
std::vector< SCH_REFERENCE > m_Refs
SCH_SHEET_PATH sheetPath
std::vector< std::pair< wxString, wxString > > variants
Hold a name of a symbol's field, field value, and default visibility.
bool FieldNamesAreDuplicates(const wxString &aLhs, const wxString &aRhs, std::initializer_list< FIELD_T > aMandatoryFields)
Test whether two field names should be treated as duplicates for the purposes of field name uniquenes...
@ USER
The field ID hasn't been set yet; field is invalid.
@ DATASHEET
name of datasheet
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
wxString GetCanonicalFieldName(FIELD_T aFieldType)
std::string path
KIBIS_MODEL * model
#define kv
#define INDETERMINATE_STATE
Used for holding indeterminate values, such as with multiple selections holding different values or c...
Definition ui_common.h:46
GROUP_COLLAPSED_DURING_SORT
Definition wx_grid.h:43
GROUP_COLLAPSED
Definition wx_grid.h:42
GROUP_EXPANDED
Definition wx_grid.h:44
GROUP_SINGLETON
Definition wx_grid.h:41