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