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