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