KiCad PCB EDA Suite
Loading...
Searching...
No Matches
lib_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 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 along
17 * with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19#include <wx/string.h>
20#include <wx/debug.h>
21#include <wx/grid.h>
22#include <wx/settings.h>
23#include <wx/brush.h>
24#include <common.h>
25#include <widgets/wx_grid.h>
26#include <sch_reference_list.h>
27#include <schematic_settings.h>
28#include "string_utils.h"
29#include <trace_helpers.h>
30
32
33
34const wxString LIB_FIELDS_EDITOR_GRID_DATA_MODEL::ITEM_NUMBER_VARIABLE = wxS( "${ITEM_NUMBER}" );
35const wxString LIB_FIELDS_EDITOR_GRID_DATA_MODEL::SYMBOL_NAME = wxS( "Symbol Name" );
36
37
38void LIB_FIELDS_EDITOR_GRID_DATA_MODEL::AddColumn( const wxString& aFieldName, const wxString& aLabel,
39 bool aAddedByUser, bool aIsCheckbox )
40{
41 // Don't add a field twice
42 if( GetFieldNameCol( aFieldName ) != -1 )
43 return;
44
45 m_cols.push_back( { aFieldName, aLabel, aAddedByUser, false, false, aIsCheckbox } );
46
47 for( LIB_SYMBOL* symbol : m_symbolsList )
48 updateDataStoreSymbolField( symbol, aFieldName );
49}
50
51
53 const wxString& aFieldName )
54{
55 int col = GetFieldNameCol( aFieldName );
56 LIB_DATA_ELEMENT& dataElement = m_dataStore[aSymbol->m_Uuid][aFieldName];
57
58 if( col != -1 && ColIsCheck( col ) )
59 {
60 dataElement.m_originalData = getAttributeValue( aSymbol, aFieldName );
61 dataElement.m_currentData = getAttributeValue( aSymbol, aFieldName );
62 dataElement.m_originallyEmpty = false;
63 dataElement.m_currentlyEmpty = false;
64 dataElement.m_isModified = false;
65 }
66 else if( const SCH_FIELD* field = aSymbol->GetField( aFieldName ) )
67 {
68 dataElement.m_originalData = field->GetText();
69 dataElement.m_currentData = field->GetText();
70 dataElement.m_originallyEmpty = false;
71 dataElement.m_currentlyEmpty = false;
72 dataElement.m_isModified = false;
73 }
74 else if( aFieldName == wxS( "Keywords" ) )
75 {
76 dataElement.m_originalData = aSymbol->GetKeyWords();
77 dataElement.m_currentData = aSymbol->GetKeyWords();
78 dataElement.m_originallyEmpty = false;
79 dataElement.m_currentlyEmpty = false;
80 dataElement.m_isModified = false;
81 }
82 else if( ColIsSymbolName( col ) )
83 {
84 dataElement.m_originalData = aSymbol->GetName();
85 dataElement.m_currentData = aSymbol->GetName();
86 dataElement.m_originallyEmpty = false;
87 dataElement.m_currentlyEmpty = false;
88 dataElement.m_isModified = false;
89 }
90 else
91 {
92 m_dataStore[aSymbol->m_Uuid][aFieldName].m_originalData = wxEmptyString;
93 m_dataStore[aSymbol->m_Uuid][aFieldName].m_currentData = wxEmptyString;
94 m_dataStore[aSymbol->m_Uuid][aFieldName].m_originallyEmpty = true;
95 m_dataStore[aSymbol->m_Uuid][aFieldName].m_currentlyEmpty = true;
96 m_dataStore[aSymbol->m_Uuid][aFieldName].m_isModified = false;
97 }
98}
99
100
102{
103 const wxString fieldName = m_cols[aCol].m_fieldName;
104
105 for( LIB_SYMBOL* symbol : m_symbolsList )
106 {
107 std::map<wxString, LIB_DATA_ELEMENT>& fieldStore = m_dataStore[symbol->m_Uuid];
108 auto it = fieldStore.find( fieldName );
109
110 if( it != fieldStore.end() )
111 {
112 it->second.m_currentData = wxEmptyString;
113 it->second.m_currentlyEmpty = true;
114 it->second.m_isModified = true;
115 fieldStore.erase( it );
116 }
117 }
118
119 m_cols.erase( m_cols.begin() + aCol );
120 m_edited = true;
121}
122
123
124void LIB_FIELDS_EDITOR_GRID_DATA_MODEL::RenameColumn( int aCol, const wxString& newName )
125{
126 for( LIB_SYMBOL* symbol : m_symbolsList )
127 {
128 // Careful; field may have already been renamed from another sheet instance
129 if( auto node = m_dataStore[symbol->m_Uuid].extract( m_cols[aCol].m_fieldName ) )
130 {
131 node.key() = newName;
132 node.mapped().m_isModified = true;
133 m_dataStore[symbol->m_Uuid].insert( std::move( node ) );
134 }
135 }
136
137 m_cols[aCol].m_fieldName = newName;
138 m_cols[aCol].m_label = newName;
139 m_edited = true;
140}
141
142
144{
145 for( size_t i = 0; i < m_cols.size(); i++ )
146 {
147 if( m_cols[i].m_fieldName == aFieldName )
148 return (int) i;
149 }
150
151 return -1;
152}
153
154
155void LIB_FIELDS_EDITOR_GRID_DATA_MODEL::SetFieldsOrder( const std::vector<wxString>& aNewOrder )
156{
157 size_t foundCount = 0;
158
159 if( aNewOrder.size() > m_cols.size() )
160 wxLogDebug( "New order contains more fields than existing columns." );
161
162 for( const wxString& newField : aNewOrder )
163 {
164 bool found = false;
165 for( size_t i = 0; i < m_cols.size() && foundCount < m_cols.size(); i++ )
166 {
167 if( m_cols[i].m_fieldName == newField )
168 {
169 std::swap( m_cols[foundCount], m_cols[i] );
170 foundCount++;
171 found = true;
172 break;
173 }
174 }
175
176 if( !found )
177 wxLogDebug( "Field '%s' not found in existing columns.", newField );
178 }
179
180 if( foundCount != m_cols.size() && foundCount != aNewOrder.size() )
181 {
182 wxLogDebug( "Not all fields in the new order were found in the existing columns." );
183 }
184}
185
186
188{
189 // Check if aCol is the first visible column
190 for( int col = 0; col < aCol; ++col )
191 {
192 if( m_cols[col].m_show )
193 return false;
194 }
195
196 return true;
197}
198
199
201{
202 GetView()->SetReadOnly( aRow, aCol, IsExpanderColumn( aCol ) );
203 return GetValue( m_rows[aRow], aCol );
204}
205
206
208{
209 std::set<wxString> mixedValues;
210 bool listMixedValues = ColIsSymbolName( aCol );
211 wxString fieldValue = INDETERMINATE_STATE;
212 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), fieldValue );
213
214 LIB_DATA_MODEL_COL& col = m_cols[aCol];
215
216 for( const LIB_SYMBOL* ref : group.m_Refs )
217 {
218 const KIID& symbolID = ref->m_Uuid;
219
220 if( !m_dataStore.contains( symbolID ) || !m_dataStore[symbolID].contains( col.m_fieldName ) )
221 return INDETERMINATE_STATE;
222
223 wxString refFieldValue = m_dataStore[symbolID][col.m_fieldName].m_currentData;
224
225 if( listMixedValues )
226 mixedValues.insert( refFieldValue );
227 else if( ref == group.m_Refs.front() )
228 fieldValue = refFieldValue;
229 else if( fieldValue != refFieldValue )
230 return INDETERMINATE_STATE;
231 }
232
233 if( listMixedValues )
234 {
235 fieldValue = wxEmptyString;
236
237 for( const wxString& value : mixedValues )
238 {
239 if( value.IsEmpty() )
240 continue;
241 else if( fieldValue.IsEmpty() )
242 fieldValue = value;
243 else
244 fieldValue += "," + value;
245 }
246 }
247
248 return fieldValue;
249}
250
251
252void LIB_FIELDS_EDITOR_GRID_DATA_MODEL::SetValue( int aRow, int aCol, const wxString& aValue )
253{
254 wxCHECK_RET( aCol >= 0 && aCol < (int) m_cols.size(), wxS( "Invalid column number" ) );
255
256 if( ColIsSymbolName( aCol ) )
257 return;
258
259 LIB_DATA_MODEL_ROW& rowGroup = m_rows[aRow];
260
261 for( const LIB_SYMBOL* ref : rowGroup.m_Refs )
262 {
263 LIB_DATA_ELEMENT& dataElement = m_dataStore[ref->m_Uuid][m_cols[aCol].m_fieldName];
264 dataElement.m_currentData = aValue;
265 dataElement.m_isModified = ( dataElement.m_currentData != dataElement.m_originalData );
266 dataElement.m_currentlyEmpty = false;
267 }
268
269 m_edited = true;
270}
271
272
274{
275 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
276 return m_cols[aCol].m_fieldName == LIB_FIELDS_EDITOR_GRID_DATA_MODEL::SYMBOL_NAME;
277}
278
279
281{
282 wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false );
283 return m_cols[aCol].m_isCheckbox;
284}
285
286
287wxGridCellAttr* LIB_FIELDS_EDITOR_GRID_DATA_MODEL::GetAttr( int aRow, int aCol, wxGridCellAttr::wxAttrKind aKind )
288{
289 wxGridCellAttr* attr = wxGridTableBase::GetAttr( aRow, aCol, aKind );
290
291 // Check for column-specific attributes first
292 if( m_colAttrs.find( aCol ) != m_colAttrs.end() && m_colAttrs[aCol] )
293 {
294 if( attr )
295 {
296 // Merge with existing attributes
297 wxGridCellAttr* newAttr = m_colAttrs[aCol]->Clone();
298
299 // Copy any existing attributes that aren't overridden
300 if( attr->HasBackgroundColour() && !newAttr->HasBackgroundColour() )
301 newAttr->SetBackgroundColour( attr->GetBackgroundColour() );
302 if( attr->HasTextColour() && !newAttr->HasTextColour() )
303 newAttr->SetTextColour( attr->GetTextColour() );
304 if( attr->HasFont() && !newAttr->HasFont() )
305 newAttr->SetFont( attr->GetFont() );
306
307 attr->DecRef();
308 attr = newAttr;
309 }
310 else
311 {
312 attr = m_colAttrs[aCol]->Clone();
313 }
314 }
315 else if( !attr )
316 {
317 attr = new wxGridCellAttr;
318 }
319
320 bool rowModified = false;
321 bool cellModified = false;
322 bool cellEmpty = true;
323 bool blankModified = false;
324
325 const wxString& fieldName = m_cols[aCol].m_fieldName;
326
327 for( const LIB_SYMBOL* ref : m_rows[aRow].m_Refs )
328 {
329 LIB_DATA_ELEMENT& element = m_dataStore[ref->m_Uuid][fieldName];
330
331 if( element.m_isModified )
332 cellModified = true;
333
334 bool elementEmpty = element.m_currentlyEmpty
335 || ( element.m_originallyEmpty && !element.m_isModified );
336
337 if( !elementEmpty )
338 cellEmpty = false;
339
340 if( element.m_currentData.IsEmpty() && element.m_isModified )
341 blankModified = true;
342
343 if( !rowModified )
344 {
345 for( const LIB_DATA_MODEL_COL& col : m_cols )
346 {
347 if( m_dataStore[ref->m_Uuid][col.m_fieldName].m_isModified )
348 {
349 rowModified = true;
350 break;
351 }
352 }
353 }
354
355 if( cellModified && rowModified && !cellEmpty )
356 break;
357 }
358
359 // Apply striped renderer for appropriate empty cells
360 if( cellEmpty && isStripeableField( aCol ) )
361 {
362 wxGridCellRenderer* stripedRenderer = getStripedRenderer( aCol );
363
364 if( stripedRenderer )
365 {
366 attr->SetRenderer( stripedRenderer );
367 attr->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
368
369 for( const LIB_SYMBOL* ref : m_rows[aRow].m_Refs )
370 {
371 if( m_dataStore[ref->m_Uuid][fieldName].m_isModified )
372 {
373 m_dataStore[ref->m_Uuid][fieldName].m_isStriped = true;
374
375 if( m_dataStore[ref->m_Uuid][fieldName].m_currentlyEmpty )
376 {
377 if( m_dataStore[ref->m_Uuid][fieldName].m_originallyEmpty )
378 {
379 attr->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOW ) );
380 }
381 else if( m_dataStore[ref->m_Uuid][fieldName].m_originalData.empty() )
382 {
383 attr->SetBackgroundColour( wxColour( 180, 220, 180 ) );
384 }
385 else
386 {
387 attr->SetBackgroundColour( wxColour( 220, 180, 180 ) );
388 }
389 }
390 else if( m_dataStore[ref->m_Uuid][fieldName].m_currentData.IsEmpty() )
391 {
392 attr->SetBackgroundColour( wxColour( 180, 200, 180 ) );
393 }
394 else
395 {
396 attr->SetBackgroundColour( wxColour( 200, 180, 180 ) );
397 }
398 }
399 }
400 }
401 }
402 else
403 {
404 bool wasStriped = false;
405
406 for( const LIB_SYMBOL* ref : m_rows[aRow].m_Refs )
407 {
408 if( m_dataStore[ref->m_Uuid][fieldName].m_isStriped )
409 {
410 wasStriped = true;
411 m_dataStore[ref->m_Uuid][fieldName].m_isStriped = false;
412 }
413 }
414
415 // If the cell was previously striped, we need to reset the attribute
416 if( wasStriped )
417 attr = new wxGridCellAttr;
418
419 if( rowModified )
420 attr->SetBackgroundColour( wxSystemSettings::GetColour( wxSYS_COLOUR_WINDOWFRAME ) );
421
422 if( blankModified )
423 attr->SetBackgroundColour( wxColour( 192, 255, 192 ) );
424 }
425
426 if( cellModified )
427 {
428 wxFont font;
429
430 if( attr->HasFont() )
431 font = attr->GetFont();
432 else if( GetView() )
433 font = GetView()->GetDefaultCellFont();
434 else
435 font = wxFont();
436
437 if( font.IsOk() )
438 {
439 font.MakeBold();
440 attr->SetFont( font );
441 }
442 }
443
444 return attr;
445}
446
447
448void LIB_FIELDS_EDITOR_GRID_DATA_MODEL::CreateDerivedSymbol( int aRow, int aCol, wxString& aNewSymbolName )
449{
450 wxCHECK_RET( aRow >= 0 && aRow < (int) m_rows.size(), "Invalid Row Number" );
451 wxCHECK_RET( aCol >= 0 && aCol < (int) m_cols.size(), "Invalid Column Number" );
452
453 const LIB_SYMBOL* parentSymbol = m_rows[aRow].m_Refs[0];
454 const wxString& fieldName = m_cols[aCol].m_fieldName;
455
456 std::map<wxString, LIB_DATA_ELEMENT>& parentFieldStore = m_dataStore[parentSymbol->m_Uuid];
457
458 // Use a special field name that won't conflict with real fields
459 wxString derivedSymbolFieldName = "__DERIVED_SYMBOL_" + fieldName + "__";
460
461 // Store derived symbol creation data under special field name so ApplyData can find it
462 LIB_DATA_ELEMENT& targetElement = parentFieldStore[derivedSymbolFieldName];
463 targetElement.m_createDerivedSymbol = true;
464 targetElement.m_derivedSymbolName = aNewSymbolName;
465 targetElement.m_currentData = aNewSymbolName;
466 targetElement.m_isModified = true;
467 targetElement.m_originalData = parentSymbol->m_Uuid.AsString();
468
469 wxLogTrace( traceLibFieldTable, "CreateDerivedSymbol: Parent symbol name='%s', UUID='%s'",
470 parentSymbol->GetName(), parentSymbol->m_Uuid.AsString() );
471 wxLogTrace( traceLibFieldTable, "CreateDerivedSymbol: Stored creation request for symbol '%s' under parent UUID %s, special field '%s'",
472 aNewSymbolName, parentSymbol->m_Uuid.AsString(), derivedSymbolFieldName );
473
474 m_edited = true;
475}
476
477void LIB_FIELDS_EDITOR_GRID_DATA_MODEL::CreateDerivedSymbolImmediate( int aRow, int aCol, wxString& aNewSymbolName )
478{
479 wxCHECK_RET( aRow >= 0 && aRow < (int) m_rows.size(), "Invalid Row Number" );
480 wxCHECK_RET( aCol >= 0 && aCol < (int) m_cols.size(), "Invalid Column Number" );
481
482 const LIB_SYMBOL* parentSymbol = m_rows[aRow].m_Refs[0];
483
484 wxLogTrace( traceLibFieldTable, "CreateDerivedSymbolImmediate: Creating '%s' from parent '%s' immediately",
485 aNewSymbolName, parentSymbol->GetName() );
486
487 // Generate a fresh UUID for the new derived symbol
488 KIID newDerivedSymbolUuid;
489
490 // Create the symbol immediately
491 createActualDerivedSymbol( parentSymbol, aNewSymbolName, newDerivedSymbolUuid );
492
493 // Rebuild the grid to show the new symbol
494 RebuildRows();
495
496 m_edited = true;
497}
498
499void LIB_FIELDS_EDITOR_GRID_DATA_MODEL::createActualDerivedSymbol( const LIB_SYMBOL* aParentSymbol, const wxString& aNewSymbolName, const KIID& aNewSymbolUuid )
500{
501 wxLogTrace( traceLibFieldTable, "createActualDerivedSymbol: Creating '%s' from parent '%s', symbol list size before: %zu",
502 aNewSymbolName, aParentSymbol->GetName(), m_symbolsList.size() );
503
504 LIB_SYMBOL* newSymbol = nullptr;
505
506 for( LIB_SYMBOL* sym : m_symbolsList )
507 {
508 if( sym->m_Uuid == aNewSymbolUuid )
509 {
510 newSymbol = sym;
511 break;
512 }
513 }
514
515 if( !newSymbol )
516 {
517 newSymbol = new LIB_SYMBOL( *aParentSymbol );
518 newSymbol->SetName( aNewSymbolName );
519
520 // Also update the VALUE field to reflect the new name for derived symbols
521 newSymbol->GetValueField().SetText( aNewSymbolName );
522
523 newSymbol->SetParent( const_cast<LIB_SYMBOL*>( aParentSymbol ) );
524 // Note: SetLib() not called here - library association handled by dialog's library manager
525 const_cast<KIID&>( newSymbol->m_Uuid ) = aNewSymbolUuid;
526 m_symbolsList.push_back( newSymbol );
527
528 wxLogTrace( traceLibFieldTable, "createActualDerivedSymbol: Added new symbol to list, size now: %zu",
529 m_symbolsList.size() );
530
531 // Initialize field data for the new symbol in the data store
532 for( const auto& col : m_cols )
533 {
534 updateDataStoreSymbolField( newSymbol, col.m_fieldName );
535 }
536
537 wxLogTrace( traceLibFieldTable, "createActualDerivedSymbol: Initialized field data for new symbol" );
538 }
539
540 // Note: Not adding to symbolLibrary directly - this will be handled by the dialog's library manager integration
541 wxString libraryName = aParentSymbol->GetLibId().GetLibNickname();
542 m_createdDerivedSymbols.emplace_back( newSymbol, libraryName );
543
544 wxLogTrace( traceLibFieldTable, "Created derived symbol '%s' for library '%s', total tracked: %zu",
545 aNewSymbolName, libraryName, m_createdDerivedSymbols.size() );
546}
547
549{
550 LIB_DATA_MODEL_ROW& rowGroup = m_rows[aRow];
551
552 for( const LIB_SYMBOL* ref : rowGroup.m_Refs )
553 {
554 auto& fieldStore = m_dataStore[ref->m_Uuid];
555
556 for( auto& [name, element] : fieldStore )
557 {
558 element.m_currentData = element.m_originalData;
559 element.m_isModified = false;
560 element.m_currentlyEmpty = false;
561 }
562 }
563
564 m_edited = false;
565
566 for( const auto& [symId, fieldStore] : m_dataStore )
567 {
568 for( const auto& [name, element] : fieldStore )
569 {
570 if( element.m_isModified )
571 {
572 m_edited = true;
573 return;
574 }
575 }
576 }
577}
578
579
581{
582 wxCHECK_RET( aCol >= 0 && aCol < (int) m_cols.size(), wxS( "Invalid column number" ) );
583
584 LIB_DATA_MODEL_ROW& rowGroup = m_rows[aRow];
585
586 for( const LIB_SYMBOL* ref : rowGroup.m_Refs )
587 {
588 LIB_DATA_ELEMENT& dataElement = m_dataStore[ref->m_Uuid][m_cols[aCol].m_fieldName];
589 if( m_cols[aCol].m_fieldName == wxS( "Keywords" ) )
590 {
591 dataElement.m_currentData = wxEmptyString;
592 dataElement.m_currentlyEmpty = false;
593 dataElement.m_isModified = ( dataElement.m_currentData != dataElement.m_originalData );
594 }
595 else
596 {
597 dataElement.m_currentData = wxEmptyString;
598 dataElement.m_currentlyEmpty = true;
599 dataElement.m_isModified = ( dataElement.m_currentData != dataElement.m_originalData )
600 || ( dataElement.m_currentlyEmpty != dataElement.m_originallyEmpty );
601 }
602 }
603
604 m_edited = true;
605}
606
607
609 const LIB_DATA_MODEL_ROW& rhGroup,
610 LIB_FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol,
611 bool ascending )
612{
613 // Empty rows always go to the bottom, whether ascending or descending
614 if( lhGroup.m_Refs.size() == 0 )
615 return true;
616 else if( rhGroup.m_Refs.size() == 0 )
617 return false;
618
619 // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
620 // to get the opposite sort. i.e. ~(a<b) != (a>b)
621 auto local_cmp =
622 [ ascending ]( const wxString& a, const wxString& b )
623 {
624 if( ascending )
625 return a < b;
626 else
627 return a > b;
628 };
629
630 // Primary sort key is sortCol; secondary is always the symbol name (column 0)
631 wxString lhs = dataModel->GetValue( lhGroup, sortCol ).Trim( true ).Trim( false );
632 wxString rhs = dataModel->GetValue( rhGroup, sortCol ).Trim( true ).Trim( false );
633
634 if( lhs == rhs && lhGroup.m_Refs.size() > 1 && rhGroup.m_Refs.size() > 1 )
635 {
636 wxString lhRef = lhGroup.m_Refs[1]->GetName();
637 wxString rhRef = rhGroup.m_Refs[1]->GetName();
638 return local_cmp( lhRef, rhRef );
639 }
640 else
641 {
642 return local_cmp( lhs, rhs );
643 }
644}
645
646
648{
650
651 // We're going to sort the rows based on their first reference, so the first reference
652 // had better be the lowest one.
653 for( LIB_DATA_MODEL_ROW& row : m_rows )
654 {
655 std::sort( row.m_Refs.begin(), row.m_Refs.end(),
656 []( const LIB_SYMBOL* lhs, const LIB_SYMBOL* rhs )
657 {
658 wxString lhs_ref( lhs->GetRef( nullptr ) );
659 wxString rhs_ref( rhs->GetRef( nullptr ) );
660 return StrNumCmp( lhs_ref, rhs_ref, true ) < 0;
661 } );
662 }
663
664 std::sort( m_rows.begin(), m_rows.end(),
665 [this]( const LIB_DATA_MODEL_ROW& lhs, const LIB_DATA_MODEL_ROW& rhs ) -> bool
666 {
667 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
668 } );
669
670 // Time to renumber the item numbers
671 int itemNumber = 1;
672
673 for( LIB_DATA_MODEL_ROW& row : m_rows )
674 {
675 row.m_ItemNumber = itemNumber++;
676 }
677
679}
680
681
683
684{
685 bool matchFound = false;
686 const KIID& lhRefID = lhRef->m_Uuid;
687 const KIID& rhRefID = rhRef->m_Uuid;
688
689 // Now check all the other columns.
690 for( size_t i = 0; i < m_cols.size(); ++i )
691 {
692 const LIB_DATA_MODEL_COL& col = m_cols[i];
693
694 if( !col.m_group )
695 continue;
696
697 if( m_dataStore[lhRefID][col.m_fieldName].m_currentData != m_dataStore[rhRefID][col.m_fieldName].m_currentData )
698 return false;
699
700 matchFound = true;
701 }
702
703 return matchFound;
704}
705
706
708 const wxString& aAttributeName )
709{
710 if( aAttributeName == wxS( "${DNP}" ) )
711 return aSymbol->GetDNP() ? wxS( "1" ) : wxS( "0" );
712
713 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
714 return aSymbol->GetExcludedFromBoard() ? wxS( "1" ) : wxS( "0" );
715
716 if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
717 return aSymbol->GetExcludedFromBOM() ? wxS( "1" ) : wxS( "0" );
718
719 if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
720 return aSymbol->GetExcludedFromSim() ? wxS( "1" ) : wxS( "0" );
721
722 if( aAttributeName == wxS( "Power" ) )
723 return aSymbol->IsPower() ? wxS( "1" ) : wxS( "0" );
724
725 if( aAttributeName == wxS( "LocalPower" ) )
726 return aSymbol->IsLocalPower() ? wxS( "1" ) : wxS( "0" );
727
728
729 return wxS( "0" );
730}
731
733 const wxString& aAttributeName,
734 const wxString& aValue )
735{
736 if( aAttributeName == wxS( "${DNP}" ) )
737 aSymbol->SetDNP( aValue == wxS( "1" ) );
738 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOARD}" ) )
739 aSymbol->SetExcludedFromBoard( aValue == wxS( "1" ) );
740 else if( aAttributeName == wxS( "${EXCLUDE_FROM_BOM}" ) )
741 aSymbol->SetExcludedFromBOM( aValue == wxS( "1" ) );
742 else if( aAttributeName == wxS( "${EXCLUDE_FROM_SIM}" ) )
743 aSymbol->SetExcludedFromSim( aValue == wxS( "1" ) );
744 else if( aAttributeName == wxS( "LocalPower" ) )
745 {
746 // Turning off local power still leaves the global flag set
747 if( aValue == wxS( "0" ) )
748 aSymbol->SetGlobalPower();
749 else
750 aSymbol->SetLocalPower();
751 }
752 else if( aAttributeName == wxS( "Power" ) )
753 {
754 if( aValue == wxS( "0" ) )
755 aSymbol->SetNormal();
756 else
757 aSymbol->SetGlobalPower();
758 }
759 else
760 wxLogDebug( "Unknown attribute name: %s", aAttributeName );
761}
762
763
765{
766 wxLogTrace( traceLibFieldTable, "RebuildRows: Starting rebuild with %zu symbols in list", m_symbolsList.size() );
767
768 if( GetView() )
769 {
770 // Commit any pending in-place edits before the row gets moved out from under
771 // the editor.
772 static_cast<WX_GRID*>( GetView() )->CommitPendingChanges( true );
773
774 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, (int) m_rows.size() );
775 GetView()->ProcessTableMessage( msg );
776 }
777
778 m_rows.clear();
779
780 wxLogTrace( traceLibFieldTable, "RebuildRows: About to process %zu symbols", m_symbolsList.size() );
781
782 for( LIB_SYMBOL* ref : m_symbolsList )
783 {
784 wxLogTrace( traceLibFieldTable, "RebuildRows: Processing symbol '%s' (UUID: %s)",
785 ref->GetName(), ref->m_Uuid.AsString() );
786
787 if( !m_filter.IsEmpty() )
788 {
789 bool match = false;
790
791 std::map<wxString, LIB_DATA_ELEMENT>& fieldStore = m_dataStore[ref->m_Uuid];
792
793 for( const LIB_DATA_MODEL_COL& col : m_cols )
794 {
795 auto it = fieldStore.find( col.m_fieldName );
796
797 if( it != fieldStore.end() && WildCompareString( m_filter, it->second.m_currentData, false ) )
798 {
799 match = true;
800 break;
801 }
802 }
803
804 if( !match )
805 continue;
806 }
807
808 bool matchFound = false;
809
810 // Performance optimization for ungrouped case to skip the N^2 for loop
811 if( !m_groupingEnabled )
812 {
813 m_rows.emplace_back( LIB_DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
814 continue;
815 }
816
817 // See if we already have a row which this symbol fits into
818 for( LIB_DATA_MODEL_ROW& row : m_rows )
819 {
820 // all group members must have identical refs so just use the first one
821 const LIB_SYMBOL* rowRef = row.m_Refs[0];
822
823 if( m_groupingEnabled && groupMatch( ref, rowRef ) )
824 {
825 matchFound = true;
826 row.m_Refs.push_back( ref );
827 row.m_Flag = GROUP_COLLAPSED;
828 break;
829 }
830 }
831
832 if( !matchFound )
833 m_rows.emplace_back( LIB_DATA_MODEL_ROW( ref, GROUP_SINGLETON ) );
834 }
835
836 if( GetView() )
837 {
838 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, (int) m_rows.size() );
839 GetView()->ProcessTableMessage( msg );
840 }
841
842 wxLogTrace( traceLibFieldTable, "RebuildRows: Completed rebuild with %zu rows created", m_rows.size() );
843 Sort();
844}
845
846
848{
849 std::vector<LIB_DATA_MODEL_ROW> children;
850
851 for( const LIB_SYMBOL* ref : m_rows[aRow].m_Refs )
852 {
853 bool matchFound = false;
854
855 if( !matchFound )
856 children.emplace_back( LIB_DATA_MODEL_ROW( ref, CHILD_ITEM ) );
857 }
858
859 if( children.size() < 2 )
860 return;
861
862 std::sort( children.begin(), children.end(),
863 [this]( const LIB_DATA_MODEL_ROW& lhs, const LIB_DATA_MODEL_ROW& rhs ) -> bool
864 {
865 return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending );
866 } );
867
868 m_rows[aRow].m_Flag = GROUP_EXPANDED;
869 m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() );
870
871 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, (int) children.size() );
872 GetView()->ProcessTableMessage( msg );
873}
874
875
877{
878 auto firstChild = m_rows.begin() + aRow + 1;
879 auto afterLastChild = firstChild;
880 int deleted = 0;
881
882 while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM )
883 {
884 deleted++;
885 afterLastChild++;
886 }
887
888 m_rows[aRow].m_Flag = GROUP_COLLAPSED;
889 m_rows.erase( firstChild, afterLastChild );
890
891 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted );
892 GetView()->ProcessTableMessage( msg );
893}
894
895
897{
899
900 if( group.m_Flag == GROUP_COLLAPSED )
901 ExpandRow( aRow );
902 else if( group.m_Flag == GROUP_EXPANDED )
903 CollapseRow( aRow );
904}
905
906
908{
909 for( size_t i = 0; i < m_rows.size(); ++i )
910 {
911 if( m_rows[i].m_Flag == GROUP_EXPANDED )
912 {
913 CollapseRow( i );
915 }
916 }
917}
918
919
921{
922 for( size_t i = 0; i < m_rows.size(); ++i )
923 {
924 if( m_rows[i].m_Flag == GROUP_COLLAPSED_DURING_SORT )
925 ExpandRow( i );
926 }
927}
928
929
930void LIB_FIELDS_EDITOR_GRID_DATA_MODEL::ApplyData( std::function<void( LIB_SYMBOL* )> symbolChangeHandler,
931 std::function<void()> postApplyHandler )
932{
933 for( LIB_SYMBOL* symbol : m_symbolsList )
934 {
935 std::map<wxString, LIB_DATA_ELEMENT>& fieldStore = m_dataStore[symbol->m_Uuid];
936
937 for( auto& srcData : fieldStore )
938 {
939 const wxString& srcName = srcData.first;
940 LIB_DATA_ELEMENT& dataElement = srcData.second;
941 const wxString& srcValue = dataElement.m_currentData;
942 int col = GetFieldNameCol( srcName );
943
944 if( dataElement.m_isModified )
945 symbolChangeHandler( symbol );
946
947 // Attributes bypass the field logic, so handle them first
948 if( col != -1 && ColIsCheck( col ) )
949 {
950 setAttributeValue( symbol, srcName, srcValue );
951 continue;
952 }
953
954 // Skip special fields with variables as names (e.g. ${QUANTITY}),
955 // they can't be edited
956 if( IsGeneratedField( srcName ) )
957 continue;
958
959 // Skip special derived symbol creation fields - these are handled separately
960 if( srcName.StartsWith( "__DERIVED_SYMBOL_" ) && srcName.EndsWith( "__" ) )
961 continue;
962
963 if( srcName == wxS( "Keywords" ) )
964 {
965 symbol->SetKeyWords( srcValue );
966 dataElement.m_originalData = dataElement.m_currentData;
967 dataElement.m_isModified = false;
968 dataElement.m_currentlyEmpty = false;
969 dataElement.m_originallyEmpty = false;
970 continue;
971 }
972
973 SCH_FIELD* destField = symbol->GetField( srcName );
974 bool userAdded = ( col != -1 && m_cols[col].m_userAdded );
975
976 // Add a not existing field if it has a value for this symbol
977 bool createField = !destField && ( !srcValue.IsEmpty() || userAdded );
978
979 if( createField )
980 {
981 const VECTOR2I symbolPos = symbol->GetPosition();
982 destField = new SCH_FIELD( symbol, FIELD_T::USER, srcName );
983 destField->SetPosition( symbolPos );
984 symbol->AddField( destField );
985 }
986
987 if( !destField )
988 continue;
989
990 if( destField->GetId() == FIELD_T::REFERENCE )
991 {
992 // Reference is not editable from this dialog
993 }
994 else if( destField->GetId() == FIELD_T::VALUE )
995 {
996 // Value field cannot be empty
997 if( !srcValue.IsEmpty() )
998 symbol->GetField( FIELD_T::VALUE )->SetText( srcValue );
999 }
1000 else if( destField->GetId() == FIELD_T::FOOTPRINT )
1001 {
1002 symbol->GetField(FIELD_T::FOOTPRINT)->SetText( srcValue );
1003 }
1004 else
1005 {
1006 destField->SetText( srcValue );
1007 }
1008
1009 dataElement.m_originalData = dataElement.m_currentData;
1010 dataElement.m_isModified = false;
1011 dataElement.m_currentlyEmpty = false;
1012 dataElement.m_originallyEmpty = dataElement.m_currentlyEmpty;
1013 }
1014
1015 std::vector<SCH_FIELD*> symbolFields;
1016 symbol->GetFields( symbolFields );
1017
1018 // Remove any fields that are not mandatory
1019 for( SCH_FIELD* field : symbolFields )
1020 {
1021 if( field->IsMandatory() )
1022 continue;
1023
1024 // Remove any fields that are not in the fieldStore
1025 if( !fieldStore.contains( field->GetName() ) )
1026 {
1027 symbolChangeHandler( symbol );
1028 symbol->RemoveField( field );
1029 delete field;
1030 }
1031 }
1032 }
1033
1034 // Process derived symbol creation requests
1035 for( auto& [symId, fieldStore] : m_dataStore )
1036 {
1037 for( auto& [fieldName, element] : fieldStore )
1038 {
1039 if( element.m_createDerivedSymbol )
1040 {
1041 const LIB_SYMBOL* parentSymbol = nullptr;
1042
1043 // First try to interpret as UUID
1044 try
1045 {
1046 KIID parentUuid( element.m_originalData );
1047
1048 for( const LIB_SYMBOL* sym : m_symbolsList )
1049 {
1050 if( sym->m_Uuid == parentUuid )
1051 {
1052 parentSymbol = sym;
1053 break;
1054 }
1055 }
1056 }
1057 catch( ... )
1058 {
1059 // Not a valid UUID, try looking up by symbol name
1060 }
1061
1062 // If UUID lookup failed, try looking up by symbol name
1063 if( !parentSymbol )
1064 {
1065 for( const LIB_SYMBOL* sym : m_symbolsList )
1066 {
1067 if( sym->GetName() == element.m_originalData )
1068 {
1069 parentSymbol = sym;
1070 break;
1071 }
1072 }
1073 }
1074
1075 if( parentSymbol )
1076 {
1077 wxString actualDerivedName = element.m_derivedSymbolName;
1078
1079 // If the derived name is the same as the parent name, auto-generate a unique name
1080 if( actualDerivedName == parentSymbol->GetName() )
1081 {
1082 // Try common variant patterns first
1083 actualDerivedName = parentSymbol->GetName() + "_1";
1084
1085 // If that exists, try incrementing the number
1086 int variant = 2;
1087 bool nameExists = true;
1088
1089 while( nameExists && variant < 100 )
1090 {
1091 nameExists = false;
1092
1093 for( const LIB_SYMBOL* sym : m_symbolsList )
1094 {
1095 if( sym->GetName() == actualDerivedName )
1096 {
1097 nameExists = true;
1098 break;
1099 }
1100 }
1101
1102 if( nameExists )
1103 {
1104 actualDerivedName = parentSymbol->GetName() + "_" + wxString::Format( "%d", variant );
1105 variant++;
1106 }
1107 }
1108 }
1109
1110 // Generate a fresh UUID for the new derived symbol (don't reuse symId which is for an existing symbol)
1111 KIID newDerivedSymbolUuid;
1112
1113 createActualDerivedSymbol( parentSymbol, actualDerivedName, newDerivedSymbolUuid );
1114 }
1115
1116 element.m_createDerivedSymbol = false;
1117 break;
1118 }
1119 }
1120 }
1121
1122 m_edited = false;
1123
1124 // Call post-apply handler if provided (for library operations and tree refresh)
1125 if( postApplyHandler )
1126 postApplyHandler();
1127}
1128
1129
1131{
1132 int width = 0;
1133 wxString fieldName = GetColFieldName( aCol ); // symbol fieldName or Qty string
1134
1135 for( const LIB_SYMBOL* symbol : m_symbolsList )
1136 {
1137 LIB_DATA_ELEMENT& text = m_dataStore[symbol->m_Uuid][fieldName];
1138
1139 width = std::max( width, KIUI::GetTextSize( text.m_currentData, GetView() ).x );
1140 }
1141
1142 return width;
1143}
1144
1145
1147{
1148 if( ColIsCheck( col ) )
1149 return wxGRID_VALUE_BOOL;
1150
1151 return wxGridTableBase::GetTypeName( row, col );
1152}
1153
1154
1156{
1157 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), nullptr );
1158
1159 const wxString& fieldName = m_cols[aCol].m_fieldName;
1160
1161 // Check if we already have a striped renderer for this field type
1162 auto it = m_stripedRenderers.find( fieldName );
1163 if( it != m_stripedRenderers.end() )
1164 {
1165 it->second->IncRef();
1166 return it->second;
1167 }
1168
1169 wxGridCellRenderer* stripedRenderer = nullptr;
1170 // Default to striped string renderer
1171 stripedRenderer = new STRIPED_STRING_RENDERER();
1172
1173 // Cache the renderer for future use - the cache owns one reference
1174 stripedRenderer->IncRef();
1175 m_stripedRenderers[fieldName] = stripedRenderer;
1176
1177 // Return with IncRef for the caller (SetRenderer will consume this reference)
1178 stripedRenderer->IncRef();
1179 return stripedRenderer;
1180}
1181
1182
1183// lib_fields_data_model.cpp - Add the isStripeableField method
1185{
1186 wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), false );
1187
1188 // Don't apply stripes to checkbox fields
1189 return !ColIsCheck( aCol );
1190}
const char * name
const KIID m_Uuid
Definition eda_item.h:516
Definition kiid.h:49
wxString AsString() const
Definition kiid.cpp:246
void SetFieldsOrder(const std::vector< wxString > &aNewOrder)
wxGridCellRenderer * getStripedRenderer(int aCol) const
void SetValue(int aRow, int aCol, const wxString &aValue) override
wxString getAttributeValue(const LIB_SYMBOL *, const wxString &aAttributeName)
std::vector< std::pair< LIB_SYMBOL *, wxString > > m_createdDerivedSymbols
static bool cmp(const LIB_DATA_MODEL_ROW &lhGroup, const LIB_DATA_MODEL_ROW &rhGroup, LIB_FIELDS_EDITOR_GRID_DATA_MODEL *dataModel, int sortCol, bool ascending)
std::map< KIID, std::map< wxString, LIB_DATA_ELEMENT > > m_dataStore
std::map< wxString, wxGridCellRenderer * > m_stripedRenderers
wxGridCellAttr * GetAttr(int row, int col, wxGridCellAttr::wxAttrKind kind) override
void CreateDerivedSymbolImmediate(int aRow, int aCol, wxString &aNewSymbolName)
wxString GetValue(int aRow, int aCol) override
void createActualDerivedSymbol(const LIB_SYMBOL *aParentSymbol, const wxString &aNewSymbolName, const KIID &aNewSymbolUuid)
std::vector< LIB_DATA_MODEL_COL > m_cols
void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser, bool aIsCheckbox)
void ApplyData(std::function< void(LIB_SYMBOL *)> symbolChangeHandler, std::function< void()> postApplyHandler=nullptr)
std::vector< LIB_DATA_MODEL_ROW > m_rows
int GetFieldNameCol(const wxString &aFieldName)
void updateDataStoreSymbolField(const LIB_SYMBOL *aSymbol, const wxString &aFieldName)
void RenameColumn(int aCol, const wxString &newName)
std::vector< LIB_SYMBOL * > m_symbolsList
bool groupMatch(const LIB_SYMBOL *lhRef, const LIB_SYMBOL *rhRef)
void CreateDerivedSymbol(int aRow, int aCol, wxString &aNewSymbolName)
void setAttributeValue(LIB_SYMBOL *aSymbol, const wxString &aAttributeName, const wxString &aValue)
wxString GetTypeName(int row, int col) override
bool IsExpanderColumn(int aCol) const override
const UTF8 & GetLibNickname() const
Return the logical library name portion of a LIB_ID.
Definition lib_id.h:87
Define a library symbol object.
Definition lib_symbol.h:85
const LIB_ID & GetLibId() const override
Definition lib_symbol.h:155
wxString GetKeyWords() const override
Definition lib_symbol.h:185
void SetGlobalPower()
bool IsPower() const override
SCH_FIELD * GetField(const wxString &aFieldName)
Find a field within this symbol matching aFieldName; return nullptr if not found.
void SetParent(LIB_SYMBOL *aParent=nullptr)
wxString GetName() const override
Definition lib_symbol.h:148
SCH_FIELD & GetValueField()
Return reference to the value field.
Definition lib_symbol.h:336
bool IsLocalPower() const override
void SetLocalPower()
virtual void SetName(const wxString &aName)
void SetNormal()
FIELD_T GetId() const
Definition sch_field.h:116
void SetPosition(const VECTOR2I &aPosition) override
void SetText(const wxString &aText) override
void SetExcludedFromBoard(bool aExcludeFromBoard) override
Set or clear exclude from board netlist flag.
Definition symbol.h:207
virtual bool GetDNP(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
Set or clear the 'Do Not Populate' flag.
Definition symbol.h:213
bool GetExcludedFromBoard() const override
Definition symbol.h:208
virtual void SetExcludedFromSim(bool aExcludeFromSim, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) override
Set or clear the exclude from simulation flag.
Definition symbol.h:177
virtual bool GetExcludedFromBOM(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
Definition symbol.h:198
virtual void SetDNP(bool aDNP, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) override
Definition symbol.h:215
virtual bool GetExcludedFromSim(const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) const override
Definition symbol.h:183
virtual void SetExcludedFromBOM(bool aExcludeFromBOM, const SCH_SHEET_PATH *aInstance=nullptr, const wxString &aVariantName=wxEmptyString) override
Set or clear the exclude from schematic bill of materials flag.
Definition symbol.h:192
std::map< int, wxGridCellAttr * > m_colAttrs
Definition wx_grid.h:86
bool IsGeneratedField(const wxString &aSource)
Returns true if the string is generated, e.g contains a single text var reference.
Definition common.cpp:137
The common library.
STRIPED_CELL_RENDERER< wxGridCellStringRenderer > STRIPED_STRING_RENDERER
const wxChar *const traceLibFieldTable
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
bool WildCompareString(const wxString &pattern, const wxString &string_to_tst, bool case_sensitive)
Compare a string against wild card (* and ?) pattern using the usual rules.
std::vector< const LIB_SYMBOL * > m_Refs
@ USER
The field ID hasn't been set yet; field is invalid.
@ 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".
wxLogTrace helper definitions.
#define INDETERMINATE_STATE
Used for holding indeterminate values, such as with multiple selections holding different values or c...
Definition ui_common.h:46
VECTOR2< int32_t > VECTOR2I
Definition vector2d.h:695
@ GROUP_COLLAPSED_DURING_SORT
Definition wx_grid.h:46
@ GROUP_EXPANDED
Definition wx_grid.h:47
@ GROUP_COLLAPSED
Definition wx_grid.h:45
@ GROUP_SINGLETON
Definition wx_grid.h:44
@ CHILD_ITEM
Definition wx_grid.h:48