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