KiCad PCB EDA Suite
Loading...
Searching...
No Matches
test_issue25112_shared_sheet_field_scope.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 The KiCad Developers, see AUTHORS.txt for contributors.
5 *
6 * This program is free software: you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation, either version 3 of the License, or (at your
9 * option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <https://www.gnu.org/licenses/>.
18 */
19
20// Fields-table regressions, including https://gitlab.com/kicad/code/kicad/-/issues/25112.
21
23
24#include <memory>
25#include <set>
26
27#include <eeschema_helpers.h>
29#include <lib_symbol.h>
31#include <locale_io.h>
32#include <richio.h>
33#include <sch_commit.h>
34#include <sch_field.h>
36#include <sch_reference_list.h>
37#include <sch_sheet.h>
38#include <sch_sheet_path.h>
39#include <sch_symbol.h>
40#include <schematic.h>
41#include <template_fieldnames.h>
42#include <tool/tool_manager.h>
43
44
45// The reproduction case instantiates level1.kicad_sch twice from the root and level2.kicad_sch
46// twice from level1, so most symbols live in a single SCH_SYMBOL reachable through several
47// sheet paths. Editing a field with the fields table scoped to one sheet only updated the data
48// store entries for the visible sheet paths; ApplyData walks every path, so the paths left out
49// of scope wrote their stale value back over the edit and the footprint appeared to revert as
50// soon as the dialog was applied.
52{
54 {
55 wxString schPath = wxString::FromUTF8( KI_TEST::GetEeschemaTestDataDir() )
56 + wxS( "issue25112/issue25112.kicad_sch" );
57
58 m_schematic.reset( EESCHEMA_HELPERS::LoadSchematic( schPath, true, false ) );
59 BOOST_REQUIRE( m_schematic != nullptr );
60
61 m_schematic->Hierarchy().GetSymbols( m_refs, SYMBOL_FILTER_NON_POWER );
62 }
63
70 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> MakeScopedModel( const wxString& aVariantName,
71 const wxString& aFieldName,
72 bool aAddedByUser = false )
73 {
74 auto model = std::make_unique<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL>( m_refs );
75
76 model->SetCurrentVariant( aVariantName );
77 model->AddColumn( GetDefaultFieldName( FIELD_T::REFERENCE, UNTRANSLATED ), wxS( "Reference" ), false );
78 model->AddColumn( aFieldName, aFieldName, aAddedByUser );
79
80 int referenceCol = model->GetFieldNameCol( GetDefaultFieldName( FIELD_T::REFERENCE, UNTRANSLATED ) );
81 BOOST_REQUIRE( referenceCol >= 0 );
82 model->SetShowColumn( referenceCol, true );
83
84 m_col = model->GetFieldNameCol( aFieldName );
85 BOOST_REQUIRE( m_col >= 0 );
86 model->SetShowColumn( m_col, true );
87
88 const SCH_REFERENCE_LIST& modelRefs = model->GetReferenceList();
89 bool found = false;
90
91 for( size_t ii = 0; ii < modelRefs.GetCount() && !found; ++ii )
92 {
93 for( size_t jj = ii + 1; jj < modelRefs.GetCount(); ++jj )
94 {
95 if( modelRefs[jj].GetSymbol() == modelRefs[ii].GetSymbol() )
96 {
97 m_symbol = modelRefs[ii].GetSymbol();
98 m_scopePath = modelRefs[ii].GetSheetPath();
99 m_siblingPath = modelRefs[jj].GetSheetPath();
100 found = true;
101 break;
102 }
103 }
104 }
105
106 BOOST_REQUIRE( found );
107
108 model->SetPath( m_scopePath );
109 model->SetScope( SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_SHEET );
110 model->RebuildRows();
111
112 m_row = -1;
113
114 for( int ii = 0; ii < model->GetNumberRows() && m_row < 0; ++ii )
115 {
116 for( const SCH_REFERENCE& ref : model->GetRowReferences( ii ) )
117 {
118 if( ref.GetSymbol() == m_symbol )
119 {
120 m_row = ii;
121 break;
122 }
123 }
124 }
125
126 BOOST_REQUIRE( m_row >= 0 );
127
128 // Sanity: the sheet scope hides the symbol's other paths, which is what left their data
129 // store entries stale.
130 BOOST_REQUIRE_EQUAL( model->GetRowReferences( m_row ).size(), 1u );
131
132 return model;
133 }
134
136 {
137 TOOL_MANAGER toolMgr;
138 SCH_COMMIT commit( &toolMgr );
139 TEMPLATES templates;
140
141 aModel.ApplyData( commit, templates );
142 }
143
145 std::unique_ptr<SCHEMATIC> m_schematic;
150 int m_col = -1;
151 int m_row = -1;
152};
153
154
155BOOST_FIXTURE_TEST_CASE( SheetScopedFieldEditSurvivesApply, ISSUE25112_FIXTURE )
156{
157 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
158 MakeScopedModel( wxEmptyString, GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED ) );
159
160 const wxString newFootprint = wxS( "Resistor_SMD:R_0603_1608Metric" );
161
162 model->SetValue( m_row, m_col, newFootprint );
163 Apply( *model );
164
165 BOOST_CHECK_EQUAL( m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText(), newFootprint );
166}
167
168
169BOOST_FIXTURE_TEST_CASE( SheetScopedFieldClearCanBeReverted, ISSUE25112_FIXTURE )
170{
171 const wxString fieldName = wxS( "MPN" );
172 const wxString fieldValue = wxS( "ABC123" );
173
174 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model = MakeScopedModel( wxEmptyString, fieldName );
175
176 SCH_FIELD field( m_symbol, FIELD_T::USER, fieldName );
177 field.SetText( fieldValue );
178 m_symbol->AddField( field );
179 model->UpdateReferences( m_refs );
180
181 BOOST_REQUIRE( !model->IsCellClear( m_row, m_col ) );
182
183 model->ClearCell( m_row, m_col );
184 BOOST_REQUIRE( model->IsCellClear( m_row, m_col ) );
185
186 model->RevertRow( m_row );
187 BOOST_CHECK( !model->IsCellClear( m_row, m_col ) );
188 BOOST_CHECK( !model->IsEdited() );
189
190 Apply( *model );
191
192 const SCH_FIELD* appliedField = m_symbol->GetField( fieldName );
193 BOOST_REQUIRE( appliedField );
194 BOOST_CHECK_EQUAL( appliedField->GetText(), fieldValue );
195}
196
197
198BOOST_FIXTURE_TEST_CASE( GroupedEditStateChecksEveryItem, ISSUE25112_FIXTURE )
199{
200 const wxString fieldName = wxS( "GroupedState" );
201 const wxString fieldValue = wxS( "Original" );
202 std::set<SCH_SYMBOL*> symbols;
203
204 for( const SCH_REFERENCE& ref : m_refs )
205 {
206 SCH_SYMBOL* symbol = ref.GetSymbol();
207
208 if( !symbols.insert( symbol ).second )
209 continue;
210
211 SCH_FIELD field( symbol, FIELD_T::USER, fieldName );
212 field.SetText( fieldValue );
213 symbol->AddField( field );
214 }
215
217 model.AddColumn( GetDefaultFieldName( FIELD_T::REFERENCE, UNTRANSLATED ), wxS( "Reference" ), false );
218 model.AddColumn( fieldName, fieldName, false );
219
220 int referenceCol = model.GetFieldNameCol( GetDefaultFieldName( FIELD_T::REFERENCE, UNTRANSLATED ) );
221 int fieldCol = model.GetFieldNameCol( fieldName );
222 BOOST_REQUIRE( referenceCol >= 0 );
223 BOOST_REQUIRE( fieldCol >= 0 );
224
225 model.SetShowColumn( referenceCol, true );
226 model.SetShowColumn( fieldCol, true );
227 model.SetGroupingEnabled( true );
228 model.SetGroupColumn( fieldCol, true );
229 model.RebuildRows();
230
231 int groupedRow = -1;
232 SCH_SYMBOL* symbolToModify = nullptr;
233
234 for( int row = 0; row < model.GetNumberRows() && groupedRow < 0; ++row )
235 {
236 const std::vector<SCH_REFERENCE> refs = model.GetRowReferences( row );
237
238 for( size_t ii = 1; ii < refs.size(); ++ii )
239 {
240 if( refs[ii].GetSymbol() != refs[0].GetSymbol() )
241 {
242 groupedRow = row;
243 symbolToModify = refs[ii].GetSymbol();
244 break;
245 }
246 }
247 }
248
249 BOOST_REQUIRE( groupedRow >= 0 );
250 BOOST_REQUIRE( symbolToModify );
251 BOOST_REQUIRE( !model.IsRowEdited( groupedRow ) );
252
253 symbolToModify->GetField( fieldName )->SetText( wxS( "Modified" ) );
254
255 BOOST_CHECK( !model.IsCellEdited( groupedRow, referenceCol ) );
256 BOOST_CHECK( model.IsCellEdited( groupedRow, fieldCol ) );
257 BOOST_CHECK( model.IsRowEdited( groupedRow ) );
258}
259
260
261// Variant field values are stored per symbol instance, so an edit made against one sheet path
262// must not be stamped onto the paths that share the symbol.
263BOOST_FIXTURE_TEST_CASE( SheetScopedVariantEditStaysOnItsPath, ISSUE25112_FIXTURE )
264{
265 const wxString variant = wxS( "Assembly" );
266
267 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
268 MakeScopedModel( variant, GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED ) );
269
270 const wxString baseFootprint = m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText();
271 const wxString newFootprint = wxS( "Resistor_SMD:R_0603_1608Metric" );
272
273 BOOST_REQUIRE( baseFootprint != newFootprint );
274
275 model->SetValue( m_row, m_col, newFootprint );
276 Apply( *model );
277
278 BOOST_CHECK_EQUAL( m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText( &m_scopePath, variant ), newFootprint );
279 BOOST_CHECK_EQUAL( m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText( &m_siblingPath, variant ), baseFootprint );
280 BOOST_CHECK_EQUAL( m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText(), baseFootprint );
281}
282
283
284BOOST_FIXTURE_TEST_CASE( CopyVariantToDefaultReachesHiddenSharedSheetPaths, ISSUE25112_FIXTURE )
285{
286 const wxString fieldName = GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED );
287 const wxString copied = wxS( "Resistor_SMD:R_0603_1608Metric" );
288 auto model = MakeScopedModel( wxS( "A" ), fieldName );
289 model->SetValue( m_row, m_col, copied );
290 model->SetCurrentVariant( wxEmptyString );
291 model->CopyCellFromVariant( m_row, m_col, wxS( "A" ) );
292
293 BOOST_CHECK_EQUAL( model->GetValue( m_row, m_col ), copied );
294 BOOST_CHECK( m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText() != copied );
295 Apply( *model );
296 BOOST_CHECK_EQUAL( m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText(), copied );
297 BOOST_CHECK_EQUAL( m_symbol->GetField( FIELD_T::FOOTPRINT )->GetText( &m_siblingPath ), copied );
298}
299
300
301BOOST_FIXTURE_TEST_CASE( CopyUsesSourceVariantsInheritedAttributes, ISSUE25112_FIXTURE )
302{
303 auto model = MakeScopedModel( wxEmptyString, wxS( "${DNP}" ) );
304 SCH_SHEET_PATH parent = m_scopePath;
305 SCH_SHEET* sheet = parent.Last();
306 parent.pop_back();
307 SCH_SHEET_VARIANT variant( wxS( "Source" ) );
308 variant.m_DNP = true;
309 sheet->AddVariant( parent, variant );
310
311 BOOST_REQUIRE( !m_scopePath.GetDNP() );
312 BOOST_REQUIRE( m_scopePath.GetDNP( wxS( "Source" ) ) );
313 BOOST_REQUIRE( model->CanCopyCellFromVariant( m_row, m_col ) );
314 model->CopyCellFromVariant( m_row, m_col, wxS( "Source" ) );
315 BOOST_CHECK_EQUAL( model->GetValue( m_row, m_col ), wxS( "1" ) );
316 BOOST_CHECK( !m_symbol->GetDNP() );
317
318 model->SetCurrentVariant( wxS( "Source" ) );
319 BOOST_CHECK( !model->CanCopyCellFromVariant( m_row, m_col ) );
320 model->CopyCellFromVariant( m_row, m_col, wxEmptyString );
321 model->SetCurrentVariant( wxEmptyString );
322 Apply( *model );
323 BOOST_CHECK( m_symbol->GetDNP() );
324}
325
326
327// The board exclusion has no variant form in SCH_REFERENCE, so it lands on the symbol even with
328// a variant selected and still has to reach the sheet paths the scope hides.
329BOOST_FIXTURE_TEST_CASE( SheetScopedBoardExclusionSurvivesApply, ISSUE25112_FIXTURE )
330{
331 const wxString variant = wxS( "Assembly" );
332
333 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
334 MakeScopedModel( variant, wxS( "${EXCLUDE_FROM_BOARD}" ) );
335
336 BOOST_REQUIRE( !m_symbol->GetExcludedFromBoard() );
337
338 model->SetValue( m_row, m_col, wxS( "1" ) );
339 Apply( *model );
340
341 BOOST_CHECK( m_symbol->GetExcludedFromBoard() );
342}
343
344
345BOOST_FIXTURE_TEST_CASE( UntouchedMissingPresetFieldRemainsAbsent, ISSUE25112_FIXTURE )
346{
347 const wxString fieldName = wxS( "UninstantiatedPresetField" );
348
349 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
350 MakeScopedModel( wxEmptyString, fieldName );
351
352 BOOST_REQUIRE( model->IsCellClear( m_row, m_col ) );
353
354 Apply( *model );
355
356 BOOST_CHECK( m_symbol->GetField( fieldName ) == nullptr );
357}
358
359
360BOOST_FIXTURE_TEST_CASE( ExplicitEmptyValueCreatesField, ISSUE25112_FIXTURE )
361{
362 const wxString fieldName = wxS( "ExplicitlyEmptyField" );
363
364 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
365 MakeScopedModel( wxEmptyString, fieldName );
366
367 BOOST_REQUIRE( model->IsCellClear( m_row, m_col ) );
368
369 model->SetValue( m_row, m_col, wxEmptyString );
370
371 BOOST_REQUIRE( !model->IsCellClear( m_row, m_col ) );
372 BOOST_REQUIRE( model->IsCellEdited( m_row, m_col ) );
373
374 Apply( *model );
375
376 const SCH_FIELD* field = m_symbol->GetField( fieldName );
377 BOOST_REQUIRE( field );
378 BOOST_CHECK( field->GetText().IsEmpty() );
379}
380
381
382BOOST_FIXTURE_TEST_CASE( ExistingEmptyFieldCanBeCleared, ISSUE25112_FIXTURE )
383{
384 const wxString fieldName = wxS( "ExistingEmptyField" );
385
386 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
387 MakeScopedModel( wxEmptyString, fieldName );
388
389 m_symbol->AddField( SCH_FIELD( m_symbol, FIELD_T::USER, fieldName ) );
390 model->UpdateReferences( m_refs );
391
392 BOOST_REQUIRE( !model->IsCellClear( m_row, m_col ) );
393 BOOST_REQUIRE( !model->IsCellEdited( m_row, m_col ) );
394
395 model->ClearCell( m_row, m_col );
396
397 BOOST_REQUIRE( model->IsCellClear( m_row, m_col ) );
398 BOOST_REQUIRE( model->IsCellEdited( m_row, m_col ) );
399
400 Apply( *model );
401
402 BOOST_CHECK( m_symbol->GetField( fieldName ) == nullptr );
403}
404
405
406BOOST_FIXTURE_TEST_CASE( UserAddedColumnCreatesEmptyField, ISSUE25112_FIXTURE )
407{
408 const wxString fieldName = wxS( "UserAddedEmptyField" );
409
410 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
411 MakeScopedModel( wxEmptyString, fieldName, true );
412
413 BOOST_REQUIRE( !model->IsCellClear( m_row, m_col ) );
414 BOOST_REQUIRE( model->IsCellEdited( m_row, m_col ) );
415
416 Apply( *model );
417
418 const SCH_FIELD* field = m_symbol->GetField( fieldName );
419 BOOST_REQUIRE( field );
420 BOOST_CHECK( field->GetText().IsEmpty() );
421}
422
423
424BOOST_FIXTURE_TEST_CASE( RevertingVariantFieldCreationRestoresAbsence, ISSUE25112_FIXTURE )
425{
426 const wxString fieldName = wxS( "RevertedVariantField" );
427
428 std::unique_ptr<SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL> model =
429 MakeScopedModel( wxS( "Assembly" ), fieldName );
430
431 model->SetValue( m_row, m_col, wxEmptyString );
432 BOOST_REQUIRE( !model->IsCellClear( m_row, m_col ) );
433
434 model->RevertRow( m_row );
435
436 BOOST_REQUIRE( model->IsCellClear( m_row, m_col ) );
437 BOOST_REQUIRE( !model->IsEdited() );
438
439 Apply( *model );
440
441 BOOST_CHECK( m_symbol->GetField( fieldName ) == nullptr );
442}
443
444
445// Repose #2771: refresh every instance of a moved symbol without replacing pending fields.
446BOOST_FIXTURE_TEST_CASE( RefreshPreservesStagedFieldsAcrossSharedSheetPaths, ISSUE25112_FIXTURE )
447{
449 auto model = MakeScopedModel( wxEmptyString, name );
450 m_symbol->GetField( FIELD_T::VALUE )->SetText( wxS( "base" ) );
451 model->UpdateReferences( m_refs );
452 model->SetValue( m_row, m_col, wxS( "staged" ) );
453
454 m_symbol->SetPosition( VECTOR2I( 100, 200 ) );
455 model->UpdateReferences( m_refs );
456 BOOST_CHECK_EQUAL( model->GetValue( m_row, m_col ), wxS( "staged" ) );
457 BOOST_CHECK( model->IsEdited() );
458 Apply( *model );
459 BOOST_CHECK_EQUAL( m_symbol->GetField( FIELD_T::VALUE )->GetText(), wxS( "staged" ) );
460 BOOST_CHECK( !model->IsEdited() );
461
462 model->SetValue( m_row, m_col, wxS( "another edit" ) );
463 m_symbol->GetField( FIELD_T::VALUE )->SetText( wxS( "external" ) );
464 model->UpdateReferences( m_refs );
465 BOOST_CHECK_EQUAL( model->GetValue( m_row, m_col ), wxS( "external" ) );
466 BOOST_CHECK( !model->IsEdited() );
467}
468
469
470BOOST_FIXTURE_TEST_CASE( RefreshPreservesFieldPresenceEditsAcrossVariantsAndPaths, ISSUE25112_FIXTURE )
471{
472 const wxString name = wxS( "PresenceAcrossVariants" );
473 auto model = MakeScopedModel( wxS( "A" ), name );
474 m_symbol->AddField( SCH_FIELD( m_symbol, FIELD_T::USER, name ) );
475 model->UpdateReferences( m_refs );
476 model->ClearCell( m_row, m_col );
477 model->SetCurrentVariant( wxS( "B" ) );
478 model->UpdateReferences( m_refs );
479 BOOST_CHECK( model->IsCellClear( m_row, m_col ) );
480 Apply( *model );
481 BOOST_CHECK( !m_symbol->GetField( name ) );
482
483 model->SetValue( m_row, m_col, wxEmptyString );
484 model->UpdateReferences( m_refs );
485 BOOST_CHECK( !model->IsCellClear( m_row, m_col ) );
486 Apply( *model );
487 BOOST_REQUIRE( m_symbol->GetField( name ) );
488 BOOST_CHECK( m_symbol->GetField( name )->GetText().IsEmpty() );
489}
490
491
492// Repose #1699 and #4001: Add Variant must activate fresh values while retaining old edits.
493BOOST_FIXTURE_TEST_CASE( NewlyAddedVariantDoesNotReceivePreviousVariantsValues, ISSUE25112_FIXTURE )
494{
496 m_schematic->AddVariant( wxS( "A" ) );
497 auto model = MakeScopedModel( wxS( "A" ), name );
498 SCH_FIELD* field = m_symbol->GetField( FIELD_T::VALUE );
499 field->SetText( wxS( "base" ) );
500 field->SetText( wxS( "A live" ), &m_scopePath, wxS( "A" ) );
501 model->UpdateReferences( m_refs );
502 model->SetValue( m_row, m_col, wxS( "A staged" ) );
503
504 m_schematic->AddVariant( wxS( "New" ) );
505 m_schematic->SetCurrentVariant( wxS( "New" ) );
506 model->SetCurrentVariant( wxS( "New" ) );
507 BOOST_CHECK_EQUAL( model->GetValue( m_row, m_col ), wxS( "base" ) );
508 BOOST_CHECK_EQUAL( field->GetText( &m_scopePath, wxS( "A" ) ), wxS( "A live" ) );
509 BOOST_CHECK( model->IsEdited() );
510
511 Apply( *model );
512 BOOST_CHECK_EQUAL( field->GetText( &m_scopePath, wxS( "New" ) ), wxS( "base" ) );
513 BOOST_CHECK_EQUAL( field->GetText( &m_scopePath, wxS( "A" ) ), wxS( "A staged" ) );
514 BOOST_CHECK_EQUAL( field->GetText( &m_siblingPath, wxS( "A" ) ), wxS( "base" ) );
515 BOOST_CHECK( !model->IsEdited() );
516}
517
518
519BOOST_FIXTURE_TEST_CASE( VariantRoundTripRetainsEditsAndCancelLeavesSchematicAlone, ISSUE25112_FIXTURE )
520{
522 auto model = MakeScopedModel( wxEmptyString, name );
523 SCH_FIELD* field = m_symbol->GetField( FIELD_T::VALUE );
524 field->SetText( wxS( "base" ) );
525 model->UpdateReferences( m_refs );
526 model->SetCurrentVariant( wxS( "A" ) );
527 model->SetValue( m_row, m_col, wxS( "A staged" ) );
528 model->SetCurrentVariant( wxS( "B" ) );
529 model->SetValue( m_row, m_col, wxS( "B staged" ) );
530 model->SetCurrentVariant( wxS( "A" ) );
531 BOOST_CHECK_EQUAL( model->GetValue( m_row, m_col ), wxS( "A staged" ) );
532 model->SetCurrentVariant( wxS( "B" ) );
533 BOOST_CHECK_EQUAL( model->GetValue( m_row, m_col ), wxS( "B staged" ) );
534 model.reset();
535 BOOST_CHECK_EQUAL( field->GetText( &m_scopePath, wxS( "A" ) ), wxS( "base" ) );
536 BOOST_CHECK_EQUAL( field->GetText( &m_scopePath, wxS( "B" ) ), wxS( "base" ) );
537}
538
539
540BOOST_FIXTURE_TEST_CASE( RenamingAndDeletingVariantsRetargetsPendingEdits, ISSUE25112_FIXTURE )
541{
543 auto model = MakeScopedModel( wxEmptyString, name );
544 SCH_FIELD* field = m_symbol->GetField( FIELD_T::VALUE );
545 field->SetText( wxS( "base" ) );
546 model->UpdateReferences( m_refs );
547 m_schematic->AddVariant( wxS( "A" ) );
548 model->SetCurrentVariant( wxS( "A" ) );
549 model->SetValue( m_row, m_col, wxS( "A staged" ) );
550 model->RenameStoredVariant( wxS( "A" ), wxS( "Renamed" ) );
551 m_schematic->RenameVariant( wxS( "A" ), wxS( "Renamed" ) );
552 BOOST_CHECK_EQUAL( model->GetCurrentVariant(), wxS( "Renamed" ) );
553 BOOST_CHECK_EQUAL( model->GetValue( m_row, m_col ), wxS( "A staged" ) );
554
555 m_schematic->AddVariant( wxS( "Deleted" ) );
556 model->SetCurrentVariant( wxS( "Deleted" ) );
557 model->SetValue( m_row, m_col, wxS( "discarded" ) );
558 model->DeleteStoredVariant( wxS( "Deleted" ) );
559 m_schematic->DeleteVariant( wxS( "Deleted" ) );
560 model->SetCurrentVariant( wxS( "Renamed" ) );
561 Apply( *model );
562
563 BOOST_CHECK_EQUAL( field->GetText( &m_scopePath, wxS( "Renamed" ) ), wxS( "A staged" ) );
564 BOOST_CHECK_EQUAL( field->GetText( &m_scopePath, wxS( "Deleted" ) ), wxS( "base" ) );
565}
566
567
568BOOST_FIXTURE_TEST_CASE( RevertingVariantKeepsBaseEditsConsistentAcrossPaths, ISSUE25112_FIXTURE )
569{
571 auto model = MakeScopedModel( wxEmptyString, name );
572 SCH_FIELD* field = m_symbol->GetField( FIELD_T::VALUE );
573 field->SetText( wxS( "base" ) );
574 model->UpdateReferences( m_refs );
575 model->SetValue( m_row, m_col, wxS( "staged base" ) );
576 model->SetCurrentVariant( wxS( "A" ) );
577 model->SetValue( m_row, m_col, wxS( "discarded variant edit" ) );
578 model->RevertRow( m_row );
579 BOOST_CHECK( model->IsEdited() );
580 model->SetCurrentVariant( wxEmptyString );
581 BOOST_CHECK_EQUAL( model->GetValue( m_row, m_col ), wxS( "staged base" ) );
582
583 Apply( *model );
584 BOOST_CHECK_EQUAL( field->GetText(), wxS( "staged base" ) );
585 BOOST_CHECK_EQUAL( field->GetText( &m_scopePath, wxS( "A" ) ), wxS( "staged base" ) );
586 BOOST_CHECK_EQUAL( field->GetText( &m_siblingPath, wxS( "A" ) ), wxS( "staged base" ) );
587 BOOST_CHECK( !model->IsEdited() );
588}
589
590
591// Repose #2772: nested field references must use the staged values for this instance/variant.
592BOOST_FIXTURE_TEST_CASE( MixedVariablesResolveStagedVariantFieldsBeforeApply, ISSUE25112_FIXTURE )
593{
594 const wxString valueName = GetDefaultFieldName( FIELD_T::VALUE, UNTRANSLATED );
595 auto model = MakeScopedModel( wxEmptyString, valueName );
596 int valueCol = m_col;
597 SCH_FIELD* field = m_symbol->GetField( FIELD_T::VALUE );
598 field->SetText( wxS( "live" ) );
599 model->UpdateReferences( m_refs );
600 model->AddColumn( wxS( "Nested" ), wxS( "Nested" ), true );
601 int nestedCol = model->GetFieldNameCol( wxS( "Nested" ) );
602 model->SetValue( m_row, nestedCol, wxS( "Value: ${vAlUe}" ) );
603 model->AddColumn( wxS( "Report" ), wxS( "Report" ), true );
604 int reportCol = model->GetFieldNameCol( wxS( "Report" ) );
605 model->SetShowColumn( reportCol, true );
606 model->SetValue( m_row, reportCol, wxS( "${REFERENCE}: ${NESTED}" ) );
607 model->SetValue( m_row, valueCol, wxS( "base staged" ) );
608 wxString prefix = m_symbol->GetRef( &m_scopePath, true ) + wxS( ": Value: " );
609 BOOST_CHECK_EQUAL( model->GetResolvedValue( m_row, reportCol ), prefix + wxS( "base staged" ) );
610 BOOST_CHECK( model->Export( BOM_FMT_PRESET() ).Contains( prefix + wxS( "base staged" ) ) );
611
612 model->SetCurrentVariant( wxS( "A" ) );
613 model->SetValue( m_row, nestedCol, wxS( "Value: ${VALUE}" ) );
614 model->SetValue( m_row, reportCol, wxS( "${REFERENCE}: ${Nested}" ) );
615 model->SetValue( m_row, valueCol, wxS( "A staged" ) );
616 BOOST_CHECK_EQUAL( model->GetResolvedValue( m_row, reportCol ), prefix + wxS( "A staged" ) );
617 model->SetCurrentVariant( wxEmptyString );
618 BOOST_CHECK_EQUAL( model->GetResolvedValue( m_row, reportCol ), prefix + wxS( "base staged" ) );
619 BOOST_CHECK_EQUAL( field->GetText(), wxS( "live" ) );
620 BOOST_CHECK_EQUAL( field->GetText( &m_siblingPath, wxS( "A" ) ), wxS( "live" ) );
621}
622
623
625{
627 m_symbol( wxS( "Test" ) ),
628 m_model( { &m_symbol } )
629 {
630 m_symbol.GetValueField().SetText( wxS( "live" ) );
631 m_symbol.GetFootprintField().SetText( wxS( "Library:Package" ) );
632 SCH_FIELD* part = new SCH_FIELD( &m_symbol, FIELD_T::USER, wxS( "PartNumber" ) );
633 part->SetText( wxS( "old part" ) );
634 m_symbol.AddField( part );
635
636 for( const wxString& name :
638 wxString( wxS( "PartNumber" ) ), wxString( wxS( "Report" ) ) } )
639 {
640 m_model.AddColumn( name, name, false );
641 m_model.SetShowColumn( m_model.GetFieldNameCol( name ), true );
642 }
643
644 m_model.RebuildRows();
645 BOOST_REQUIRE_EQUAL( m_model.GetNumberRows(), 1 );
646 }
647
650};
651
652
653BOOST_FIXTURE_TEST_SUITE( LibFieldsTableTextVars, LIB_FIELDS_TABLE_TEXT_VARS_FIXTURE )
654
655
656BOOST_AUTO_TEST_CASE( DerivedFieldsDisplayPendingInheritanceWithoutCreatingOverrides )
657{
658 m_symbol.SetKeyWords( wxS( "original keywords" ) );
659 m_symbol.GetDescriptionField().SetText( wxS( "Monostable" ) );
660 LIB_SYMBOL child( wxS( "Child" ) );
661 LIB_SYMBOL grandchild( wxS( "Grandchild" ) );
662 child.GetDescriptionField().SetText( wxS( "Monostable" ) );
663 child.GetValueField().SetText( wxS( "Child" ) );
664 child.SetParent( &m_symbol );
665 grandchild.SetParent( &child );
666 LIB_FIELDS_EDITOR_GRID_DATA_MODEL model( { &m_symbol, &child, &grandchild } );
667
668 const wxString footprint = GetDefaultFieldName( FIELD_T::FOOTPRINT, UNTRANSLATED );
670 model.AddColumn( footprint, footprint, false );
671 model.SetShowColumn( 0, true );
672 model.AddColumn( keywords, keywords, false );
673 model.SetShowColumn( 1, true );
674 model.AddColumn( wxS( "PartNumber" ), wxS( "PartNumber" ), false );
675 const wxString description = GetDefaultFieldName( FIELD_T::DESCRIPTION, UNTRANSLATED );
676 const wxString value = GetDefaultFieldName( FIELD_T::VALUE, UNTRANSLATED );
678 model.AddColumn( description, description, false );
679 model.AddColumn( value, value, false );
680 model.AddColumn( name, name, false );
681 model.RebuildRows();
682
683 auto rowFor = [&]( const LIB_SYMBOL* aSymbol )
684 {
685 for( int row = 0; row < model.GetNumberRows(); ++row )
686 {
687 if( model.GetSymbolForRow( row ) == aSymbol )
688 return row;
689 }
690
691 return -1;
692 };
693
694 const int parentRow = rowFor( &m_symbol );
695 const int childRow = rowFor( &child );
696 const int grandchildRow = rowFor( &grandchild );
697 BOOST_REQUIRE( parentRow >= 0 && childRow >= 0 && grandchildRow >= 0 );
698
699 BOOST_CHECK( !model.CanUseParentValue( parentRow, 0 ) );
700 BOOST_CHECK( !model.CanUseParentValue( parentRow, 1 ) );
701 BOOST_CHECK( !model.CanUseParentValue( childRow, 0 ) );
702 BOOST_CHECK( !model.CanUseParentValue( childRow, 1 ) );
703 BOOST_CHECK( !model.CanUseParentValue( childRow, 2 ) ); // User field
704 BOOST_CHECK( model.CanUseParentValue( childRow, 3 ) ); // Explicit description matching parent
705 BOOST_CHECK( !model.CanUseParentValue( childRow, 4 ) ); // Value must remain non-empty
706 BOOST_CHECK( !model.CanUseParentValue( childRow, 5 ) ); // Symbol name is read-only
707 model.UseParentValue( parentRow, 0 );
708 model.UseParentValue( childRow, 4 );
709 BOOST_CHECK_EQUAL( model.GetValue( parentRow, 0 ), wxS( "Library:Package" ) );
710 BOOST_CHECK_EQUAL( model.GetValue( childRow, 4 ), wxS( "Child" ) );
711
712 BOOST_CHECK( model.GetValue( childRow, 1 ).IsEmpty() );
713 BOOST_CHECK_EQUAL( model.GetResolvedValue( grandchildRow, 1 ), wxS( "original keywords" ) );
714
715 int modified = 0;
716 model.ApplyData( [&]( LIB_SYMBOL* ) { ++modified; } );
717 BOOST_CHECK_EQUAL( modified, 0 );
718 BOOST_CHECK( child.GetRawKeyWords().IsEmpty() );
719 BOOST_CHECK( grandchild.GetRawKeyWords().IsEmpty() );
720
721 model.SetValue( parentRow, 0, wxS( "Package_DIP:DIP-16_W7.62mm" ) );
722 model.SetValue( parentRow, 1, wxS( "staged keywords" ) );
723 model.SetValue( parentRow, 3, wxS( "New description" ) );
724 BOOST_CHECK_EQUAL( model.GetResolvedValue( childRow, 3 ), wxS( "Monostable" ) );
725 BOOST_CHECK( model.GetValue( childRow, 0 ).IsEmpty() );
726 BOOST_CHECK_EQUAL( model.GetResolvedValue( childRow, 0 ), wxS( "Package_DIP:DIP-16_W7.62mm" ) );
727 BOOST_CHECK_EQUAL( model.GetResolvedValue( grandchildRow, 0 ), model.GetResolvedValue( childRow, 0 ) );
728 BOOST_CHECK_EQUAL( model.GetResolvedValue( grandchildRow, 1 ), wxS( "staged keywords" ) );
729
730 model.SetValue( childRow, 0, wxS( "Package_SO:SOIC-16" ) );
731 model.SetValue( childRow, 1, wxS( "child keywords" ) );
732 model.SetValue( parentRow, 0, wxS( "Package_DIP:DIP-16" ) );
733 model.SetValue( parentRow, 1, wxS( "new parent keywords" ) );
734 BOOST_CHECK_EQUAL( model.GetResolvedValue( grandchildRow, 0 ), wxS( "Package_SO:SOIC-16" ) );
735 BOOST_CHECK_EQUAL( model.GetResolvedValue( grandchildRow, 1 ), wxS( "child keywords" ) );
736
737 BOOST_CHECK( model.CanUseParentValue( childRow, 0 ) );
738 BOOST_CHECK( model.CanUseParentValue( childRow, 1 ) );
739 model.UseParentValue( childRow, 0 );
740 model.UseParentValue( childRow, 1 );
741 model.UseParentValue( childRow, 3 );
742 BOOST_CHECK( !model.CanUseParentValue( childRow, 3 ) );
743 BOOST_CHECK_EQUAL( model.GetResolvedValue( grandchildRow, 3 ), wxS( "New description" ) );
744 BOOST_CHECK_EQUAL( model.GetResolvedValue( grandchildRow, 0 ), wxS( "Package_DIP:DIP-16" ) );
745 BOOST_CHECK_EQUAL( model.GetResolvedValue( grandchildRow, 1 ), wxS( "new parent keywords" ) );
746 BOOST_CHECK( model.Export( BOM_FMT_PRESET() ).Contains( wxS( "Package_DIP:DIP-16" ) ) );
747 BOOST_CHECK( model.Export( BOM_FMT_PRESET() ).Contains( wxS( "new parent keywords" ) ) );
748
749 model.ApplyData( [&]( LIB_SYMBOL* ) { ++modified; } );
750 BOOST_CHECK_EQUAL( modified, 2 ); // Parent edits and the child's description override
751 BOOST_CHECK( child.GetFootprintField().GetText().IsEmpty() );
752 BOOST_CHECK( grandchild.GetFootprintField().GetText().IsEmpty() );
753 BOOST_CHECK( child.GetDescriptionField().GetText().IsEmpty() );
754 BOOST_CHECK_EQUAL( child.GetDescription(), wxS( "New description" ) );
755 BOOST_CHECK( child.GetRawKeyWords().IsEmpty() );
756 BOOST_CHECK( grandchild.GetRawKeyWords().IsEmpty() );
757 BOOST_CHECK_EQUAL( grandchild.GetKeyWords(), wxS( "new parent keywords" ) );
758 BOOST_CHECK_EQUAL( grandchild.Flatten()->GetFootprintField().GetText(), wxS( "Package_DIP:DIP-16" ) );
759 BOOST_CHECK_EQUAL( model.GetResolvedValue( grandchildRow, 0 ), wxS( "Package_DIP:DIP-16" ) );
760
761 // Saving the child must not turn its inherited keywords into a local override either.
762 STRING_FORMATTER formatter;
763 SCH_IO_KICAD_SEXPR::FormatLibSymbol( &grandchild, formatter );
764 BOOST_CHECK( formatter.GetString().find( "ki_keywords" ) == std::string::npos );
765
766 LIB_FIELDS_EDITOR_GRID_DATA_MODEL childOnlyModel( { &grandchild } );
767 childOnlyModel.AddColumn( keywords, keywords, false );
768 childOnlyModel.RebuildRows();
769 BOOST_CHECK_EQUAL( childOnlyModel.GetResolvedValue( 0, 0 ), wxS( "new parent keywords" ) );
770
771 // A grouped cell containing a root must not offer to clear its value.
772 model.SetValue( childRow, 1, wxS( "new parent keywords" ) );
773 model.SetValue( grandchildRow, 1, wxS( "new parent keywords" ) );
774 model.SetGroupingEnabled( true );
775 model.SetGroupColumn( 1, true );
776 model.RebuildRows();
777 BOOST_REQUIRE_EQUAL( model.GetNumberRows(), 1 );
778 BOOST_CHECK( !model.CanUseParentValue( 0, 1 ) );
779 model.UseParentValue( 0, 1 );
780 BOOST_CHECK_EQUAL( model.GetValue( 0, 1 ), wxS( "new parent keywords" ) );
781
782 // A group containing only derived symbols can discard all its local overrides.
783 model.SetFilter( wxS( "*child*" ) );
784 model.RebuildRows();
785 BOOST_REQUIRE_EQUAL( model.GetNumberRows(), 1 );
786 BOOST_REQUIRE_EQUAL( model.GetRowReferences( 0 ).size(), 2 );
787 BOOST_CHECK( model.CanUseParentValue( 0, 1 ) );
788 model.UseParentValue( 0, 1 );
789 BOOST_CHECK( model.GetValue( 0, 1 ).IsEmpty() );
790 BOOST_CHECK_EQUAL( model.GetResolvedValue( 0, 1 ), wxS( "new parent keywords" ) );
791}
792
793
794BOOST_AUTO_TEST_CASE( MixedVariablesResolveStagedFieldsAndLiveFallbacks )
795{
796 m_model.SetValue( 0, 1, wxS( "staged" ) );
797 m_model.SetValue( 0, 2, wxS( "Part ${vAlUe}" ) );
798 m_model.SetValue( 0, 3, wxS( "${PARTNUMBER}: ${FOOTPRINT_LIBRARY} ${Unknown}" ) );
799 const wxString expected = wxS( "Part staged: Library ${Unknown}" );
800
801 BOOST_CHECK_EQUAL( m_model.GetResolvedValue( 0, 3 ), expected );
802 BOOST_CHECK( m_model.Export( BOM_FMT_PRESET() ).Contains( expected ) );
803 BOOST_CHECK_EQUAL( m_symbol.GetValueField().GetText(), wxS( "live" ) );
804 BOOST_CHECK_EQUAL( m_symbol.GetField( wxS( "PartNumber" ) )->GetText(), wxS( "old part" ) );
805}
806
807
808BOOST_AUTO_TEST_CASE( StagedVariableCyclesRemainBounded )
809{
810 m_model.SetValue( 0, 1, wxS( "${PartNumber}" ) );
811 m_model.SetValue( 0, 2, wxS( "${VALUE}" ) );
812 m_model.SetValue( 0, 3, wxS( "Value: ${VALUE}" ) );
813 wxString resolved = m_model.GetResolvedValue( 0, 3 );
814 BOOST_CHECK( resolved == wxS( "Value: ${VALUE}" ) || resolved == wxS( "Value: ${PartNumber}" ) );
815 m_model.SetValue( 0, 1, wxS( "${VALUE}" ) );
816 BOOST_CHECK_EQUAL( m_model.GetResolvedValue( 0, 3 ), wxS( "Value: ${VALUE}" ) );
817}
818
819
const char * name
static SCHEMATIC * LoadSchematic(const wxString &aFileName, bool aSetActive, bool aForceDefaultProject, PROJECT *aProject=nullptr, bool aCalculateConnectivity=true, REPORTER *aRootReporter=nullptr)
void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser) override
Define a library symbol object.
Definition lib_symbol.h:114
const wxString & GetRawKeyWords() const
Return only this symbol's keywords, without inheriting from its parent.
Definition lib_symbol.h:218
SCH_FIELD & GetDescriptionField()
Definition lib_symbol.h:463
wxString GetDescription() const override
Get the Description field text value *‍/.
Definition lib_symbol.h:202
wxString GetKeyWords() const override
Definition lib_symbol.h:220
SCH_FIELD & GetFootprintField()
Definition lib_symbol.h:455
void SetParent(LIB_SYMBOL *aParent=nullptr)
SCH_FIELD & GetValueField()
Definition lib_symbol.h:447
std::unique_ptr< LIB_SYMBOL > Flatten() const
Return a flattened symbol inheritance to the caller.
void AddField(SCH_FIELD *aField)
Add a field.
Instantiate the current locale within a scope in which you are expecting exceptions to be thrown.
Definition locale_io.h:37
Commit for the schematic and symbol editors.
Definition sch_commit.h:54
virtual const wxString & GetText() const override
Return the string associated with the text object.
Definition sch_field.h:139
void SetText(const wxString &aText) override
static void FormatLibSymbol(LIB_SYMBOL *aPart, OUTPUTFORMATTER &aFormatter)
Container to create a flattened list of symbols because in a complex hierarchy, a symbol can be used ...
A helper to define a symbol's reference designator in a schematic.
Handle access to a stack of flattened SCH_SHEET objects by way of a path for creating a flattened sch...
SCH_SHEET * Last() const
Return a pointer to the last SCH_SHEET of the list.
void pop_back()
Forwarded method from std::vector.
Variant information for a schematic sheet.
Sheet symbol placed in a schematic, and is the entry point for a sub schematic.
Definition sch_sheet.h:48
void AddVariant(const SCH_SHEET_PATH &aInstance, const SCH_SHEET_VARIANT &aVariant)
Schematic symbol object.
Definition sch_symbol.h:73
SCH_FIELD * AddField(const SCH_FIELD &aField)
Add a field to the symbol.
SCH_FIELD * GetField(FIELD_T aFieldType)
Return a mandatory field in this symbol.
Implement an OUTPUTFORMATTER to a memory buffer.
Definition richio.h:430
const std::string & GetString()
Definition richio.h:453
void ApplyData(SCH_COMMIT &aCommit, TEMPLATES &aTemplateFieldnames)
Master controller class:
std::string GetEeschemaTestDataDir()
Get the configured location of Eeschema test data.
Definition of the SCH_SHEET_PATH and SCH_SHEET_LIST classes for Eeschema.
@ SYMBOL_FILTER_NON_POWER
void Apply(SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL &aModel)
std::unique_ptr< SYMBOL_FIELDS_EDITOR_GRID_DATA_MODEL > MakeScopedModel(const wxString &aVariantName, const wxString &aFieldName, bool aAddedByUser=false)
Build a model scoped to the first sheet path of a symbol that a later path also reaches.
wxString GetDefaultFieldName(FIELD_T aFieldId, TRANSLATION aTranslation)
Return a default symbol field name for a mandatory field type.
@ USER
The field ID hasn't been set yet; field is invalid.
@ DESCRIPTION
Field Description of part, i.e. "1/4W 1% Metal Film Resistor".
@ FOOTPRINT
Field Name Module PCB, i.e. "16DIP300".
@ REFERENCE
Field Reference of part, i.e. "IC21".
@ VALUE
Field Value of part, i.e. "3.3K".
@ UNTRANSLATED
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_FIXTURE_TEST_CASE(SheetScopedFieldEditSurvivesApply, ISSUE25112_FIXTURE)
BOOST_AUTO_TEST_CASE(DerivedFieldsDisplayPendingInheritanceWithoutCreatingOverrides)
bool copied
KIBIS_MODEL * model
VECTOR3I expected(15, 30, 45)
BOOST_CHECK_EQUAL(result, "25.4")
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:708