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