KiCad PCB EDA Suite
Loading...
Searching...
No Matches
fields_table_data_model.h
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 The 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
20#pragma once
21
22#include <set>
23#include <unordered_set>
24#include <vector>
25#include <algorithm>
26#include <map>
27
28#include <widgets/wx_grid.h>
29#include <widgets/ui_common.h>
30
31#include <common.h>
32#include <eda_pattern_match.h>
33#include <kiid.h>
34#include <refdes_utils.h>
36#include <string_utils.h>
37#include <wx/debug.h>
38
39
41{
42inline const wxColour VARIANT_SYMBOL_OVERRIDE_DARK_BLUE( 40, 60, 80 );
43inline const wxColour VARIANT_SYMBOL_OVERRIDE_LIGHT_BLUE( 220, 235, 255 );
44inline const wxColour VARIANT_FIELD_OVERRIDE_DARK_YELLOW( 80, 80, 40 );
45inline const wxColour VARIANT_FIELD_OVERRIDE_LIGHT_YELLOW( 255, 255, 200 );
46inline const wxColour TEXT_VARIABLE_DARK_AMBER( 80, 70, 30 );
47inline const wxColour TEXT_VARIABLE_LIGHT_YELLOW( 255, 252, 200 );
48inline const wxColour STRIPED_CLEARED_EMPTY_FIELD_LIGHT_GREEN( 180, 220, 180 );
49inline const wxColour STRIPED_CLEARED_NONEMPTY_FIELD_LIGHT_RED( 220, 180, 180 );
50inline const wxColour STRIPED_EDITED_EMPTY_FIELD_MUTED_GREEN( 180, 200, 180 );
51inline const wxColour STRIPED_EDITED_NONEMPTY_FIELD_MUTED_RED( 200, 180, 180 );
52inline const wxColour EDITED_EMPTY_FIELD_BRIGHT_GREEN( 192, 255, 192 );
53inline const wxColour CLEARED_FIELD_STRIPE_ON_DARK_LIGHT_RED( 220, 180, 180 );
54inline const wxColour CLEARED_FIELD_STRIPE_ON_LIGHT_DARK_RED( 100, 10, 10 );
55} // namespace FIELDS_TABLE_COLOR
56
57
74
90
92{
93 wxString m_fieldName;
94 wxString m_label;
96 bool m_show;
97 bool m_group;
98};
99
100
101template <typename ITEM_TYPE>
103{
104 DATA_MODEL_ROW( const ITEM_TYPE& aFirstItem, ROW_STATE aGroupingState )
105 {
106 m_itemNumber = 0;
107 m_items.push_back( aFirstItem );
108 m_state = aGroupingState;
109 }
110
113 std::vector<ITEM_TYPE> m_items;
114};
115
116
122{
123public:
126
127 static const wxString QUANTITY_VARIABLE;
128 static const wxString ITEM_NUMBER_VARIABLE;
129
130 bool IsEdited() { return m_edited; }
131
132 virtual void AddColumn( const wxString& aFieldName, const wxString& aLabel, bool aAddedByUser ) = 0;
133
134 void MoveColumn( int aCol, int aNewPos );
135 void RemoveColumn( int aCol );
136 void RenameColumn( int aCol, const wxString& newName );
137
138 int GetNumberCols() override { return static_cast<int>( m_cols.size() ); }
139
140 void SetColLabelValue( int aCol, const wxString& aLabel ) override;
141 wxString GetColLabelValue( int aCol ) override;
142 wxString GetColFieldName( int aCol );
143 int GetColDataWidth( int aCol );
144 int GetFieldNameCol( const wxString& aFieldName ) const;
145
146 std::vector<BOM_FIELD> GetFieldsOrdered();
147 void SetFieldsOrder( const std::vector<wxString>& aNewOrder );
148
149 bool IsEmptyCell( int aRow, int aCol ) override
150 {
151 return false; // don't allow adjacent cell overflow, even if we are actually empty
152 }
153
154 bool ColIsReference( int aCol ) const;
155 bool ColIsQuantity( int aCol ) const;
156 bool ColIsItemNumber( int aCol ) const;
157 bool ColIsValue( int aCol ) const;
158 bool ColIsFootprint( int aCol ) const;
159 // Whether a column is something like DNP, e.g. one of the checkboxes in the Attributes section
160 // of symbols/footprints
161 bool ColIsAttribute( int aCol ) const;
162 // Whether a column is an attribute, or a special property like ${SYMBOL_NAME}
163 bool ColIsItemProperty( int aCol ) const;
164 // Whether a column is magic variable that is calculated or resolved, e.g. ${QUANTITY}, ${ITEM_NUMBER},
165 // but not something like ${SYMBOL_NAME}.
166 // The important part is that the column’s value is derived at read time rather than staged as stored data
167 bool ColIsComputed( int aCol ) const;
168
173 virtual bool ColIsItemIdentifier( int aCol ) const { return ColIsReference( aCol ); }
174
175 virtual bool ColIsReadOnly( int aCol ) const;
176 bool IsExpanderColumn( int aCol ) const override;
177 virtual bool IsCellReadOnly( int aRow, int aCol );
178
179 void SetSorting( int aCol, bool aAscending );
180 int GetSortCol() { return m_sortColumn; }
181 bool GetSortAsc() { return m_sortAscending; }
182
183 // These are used to disable RebuildRows() while generating batches of UI events, e.g.
184 // applying a BOM preset, that would otherwise thrash the grid.
185 void EnableRebuilds();
186 void DisableRebuilds();
187 virtual void RebuildRows() = 0;
188
189 void SetFilter( const wxString& aFilter ) { m_filter = aFilter; }
190 const wxString& GetFilter() { return m_filter; }
191 void SetFilterScope( BOM_FILTER_SCOPE aScope ) { m_filterScope = aScope; }
193
194 void SetSelectionItems( const std::unordered_set<KIID_PATH>& aItems ) { m_selectionItems = aItems; }
196
197 void SetGroupingEnabled( bool aGroup ) { m_groupingEnabled = aGroup; }
199
200 /* These contradictorily named functions force including items that have the Exclude from
201 * BOM flag set. This is needed so they can be viewed in a fields table while still being
202 * excluded from BOM export.
203 */
204 void SetIncludeExcludedFromBOM( bool aInclude ) { m_includeExcluded = aInclude; }
206
207 void SetExcludeDNP( bool aExclude ) { m_excludeDNP = aExclude; }
208 bool GetExcludeDNP() { return m_excludeDNP; }
209
210 void SetGroupColumn( int aCol, bool aGroup );
211 bool GetGroupColumn( int aCol );
212
213 void SetShowColumn( int aCol, bool aShow );
214 bool GetShowColumn( int aCol );
215
216 void ApplyBomPreset( const BOM_PRESET& aPreset );
217 BOM_PRESET GetBomSettings();
218 wxString Export( const BOM_FMT_PRESET& aSettings );
219
220 virtual wxString GetResolvedValue( int aRow, int aCol ) = 0;
221 virtual wxString GetExportValue( int aRow, int aCol, const wxString& aRefDelimiter,
222 const wxString& aRefRangeDelimiter ) = 0;
223
224 virtual void ClearCell( int aRow, int aCol ) = 0;
225 bool CanClearCell( int aRow, int aCol );
226 virtual bool IsCellClear( int aRow, int aCol ) = 0;
227 virtual bool IsCellEdited( int aRow, int aCol ) = 0;
229 bool IsRowEdited( int aRow );
230 virtual void RevertRow( int aRow ) = 0;
231
233 virtual std::vector<KIID_PATH> GetRowItemKeys( int aRow ) const = 0;
234
243 void SetCurrentVariant( const wxString& aVariantName ) { m_currentVariant = aVariantName; }
244 const wxString& GetCurrentVariant() const { return m_currentVariant; }
245
246 void SetVariantNames( const std::vector<wxString>& aVariantNames ) { m_variantNames = aVariantNames; }
247 const std::vector<wxString>& GetVariantNames() const { return m_variantNames; }
248
249 // Identity-based undo serialization (keyed by symbol, not row position) for the dialog's
250 // Ctrl+Z, so it stays correct as rows are grouped/sorted/reordered.
251 bool HasUndoStateSerialization() const override { return true; }
252 wxString SerializeUndoState() const override;
253 void RestoreUndoState( const wxString& aState ) override;
254
255
256 // Stuff this class knows nothing about, but needs to guarantee that the templated class implements
257 virtual void ExpandCollapseRow( int aRow ) = 0;
258
259protected:
260 // One of the special field names like ${DNP} that refer to the Attributes in symbols/footprints
261 virtual bool fieldIsAttribute( const wxString& aFieldName ) const;
262 // One of the special field names like ${SYMBOL_KEYWORDS} that refer to sometimes-editable
263 // properties in the symbol/footprint itself. Especially used for lib symbols/footprints,
264 // that have properties that overlap with mandatory fields, like lib footprints having
265 // a library description property as well as a mandatory description field
266 virtual bool fieldIsItemProperty( const wxString& aFieldName ) const;
267
268 // Helper function to translate named attribute values like ${DNP}.
269 virtual wxString getAttributeResolvedValue( const wxString& aFieldName, bool aValue ) const;
270
271 bool cellUsesResolvedTextRenderer( int aRow, int aCol );
272 void applyResolvedTextRenderer( wxGridCellAttr* aAttr, bool aApplyTint );
273
274 bool cellUsesUrlEditor( int aRow, int aCol );
275 wxGridCellAttr* cloneUrlEditorAttr();
276
277 wxGridCellAttr* applyCellDecorations( wxGridCellAttr* aAttr, int aRow, int aCol );
278 wxGridCellAttr* applyFieldPresenceRenderer( wxGridCellAttr* aAttr, int aRow, int aCol );
279
280 // Column mutations invalidate the position-based coordinates held by an open wxGrid editor.
282
283protected:
284 wxGridCellRenderer* m_stripedRenderer;
285 wxGridCellRenderer* m_resolvedTextRenderer;
286
290 wxString m_filter;
296
298 std::unordered_set<KIID_PATH> m_selectionItems;
299
301 std::vector<wxString> m_variantNames;
302
303
304 std::vector<DATA_MODEL_COL> m_cols;
305
306 // Data store
307 //
308 // This is the storage of the dialog's currently edited field values for each item in the table.
309 // Having a store separate from the live values in the symbols/footprints objects
310 // allows us to keep our edits separate from the actual sch/pcb until the user explicitly
311 // applies them.
312 //
313 // Items (symbols/footprints) are both identified by KIID_PATH, which for a symbol
314 // is the sheet path + symbol UUID (symbols have multiple instances),
315 // and for a footprint is the footprint UUID.
316 //
317 // m_rows and m_cols are just a generated view based on the data store,
318 // and are rebuilt as the user changes grouping, sorting, filtering, etc.
319 //
320 // NOTE: be very careful about how you "read" this data store, you should
321 // use getDataStoreFieldValue() to read values from the data store.
322 //
323 // The map is used to distinguish between present-but-empty vs. not-present.
324 //
325 // Use the get/set/clear/update/initialize functions to access the data store,
326 // rather than accessing it directly, as using [] can unintentionally create
327 // a present-but-empty field when you just want to check if it is present.
328 std::map<KIID_PATH, std::map<wxString, wxString>> m_dataStore;
329};
330
331
337class GRID_CELL_RESOLVED_TEXT_RENDERER : public wxGridCellStringRenderer
338{
339public:
341
342 void Draw( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC& aDC, const wxRect& aRect, int aRow, int aCol,
343 bool isSelected ) override;
344
345 wxSize GetBestSize( wxGrid& aGrid, wxGridCellAttr& aAttr, wxDC& aDC, int aRow, int aCol ) override;
346
347 wxGridCellRenderer* Clone() const override;
348};
349
350
356template <typename ITEM_TYPE>
358{
359public:
360 void AddColumn( const wxString& aFieldName, const wxString& aLabel,
361 bool aAddedByUser ) override
362 {
363 // Don't add a field twice
364 if( GetFieldNameCol( aFieldName ) != -1 )
365 return;
366
368
369 m_cols.push_back( { aFieldName, aLabel, aAddedByUser, false, false } );
370
371 for( const ITEM_TYPE& item : getAllItems() )
372 initializeDataStoreItemField( item, m_cols.back() );
373
374 if( aAddedByUser )
375 m_edited = true;
376
377 if( wxGrid* grid = GetView() )
378 {
379 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_COLS_APPENDED, 1 );
380 grid->ProcessTableMessage( msg );
381 }
382 }
383
384
388 void ClearCell( int aRow, int aCol ) override
389 {
390 wxCHECK_RET( aRow >= 0 && aRow < static_cast<int>( m_rows.size() ), "Invalid Row Number" );
391 wxCHECK_RET( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), "Invalid Column Number" );
392
393 if( !CanClearCell( aRow, aCol ) )
394 return;
395
396 const wxString& fieldName = m_cols[aCol].m_fieldName;
397
398 for( const ITEM_TYPE& item : m_rows[aRow].m_items )
399 clearStoredField( item, fieldName );
400
401 m_edited = true;
402 }
403
404
409 bool IsCellClear( int aRow, int aCol ) override
410 {
411 wxCHECK_MSG( aRow >= 0 && aRow < static_cast<int>( m_rows.size() ), false, "Invalid Row Number" );
412 wxCHECK_MSG( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false, "Invalid Column Number" );
413
414 for( const ITEM_TYPE& item : m_rows[aRow].m_items )
415 {
416 wxString unused;
417
418 if( getStoredFieldValue( item, m_cols[aCol].m_fieldName, unused ) )
419 return false;
420 }
421
422 return true;
423 }
424
425
430 bool IsCellEdited( int aRow, int aCol ) override
431 {
432 wxCHECK_MSG( aRow >= 0 && aRow < static_cast<int>( m_rows.size() ), false, "Invalid Row Number" );
433 wxCHECK_MSG( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), false, "Invalid Column Number" );
434
435 for( const ITEM_TYPE& item : m_rows[aRow].m_items )
436 {
437 if( fieldIsModified( item, m_cols[aCol].m_fieldName ) )
438 return true;
439 }
440
441 return false;
442 }
443
444
445 int GetNumberRows() override { return (int) m_rows.size(); }
446 ROW_STATE GetRowState( int aRow ) const override { return m_rows[aRow].m_state; }
447
448
449 std::vector<KIID_PATH> GetRowItemKeys( int aRow ) const override
450 {
451 wxCHECK( aRow >= 0 && aRow < (int) m_rows.size(), std::vector<KIID_PATH>() );
452
453 std::vector<KIID_PATH> keys;
454
455 for( const ITEM_TYPE& item : m_rows[aRow].m_items )
456 keys.push_back( getDataStoreKey( item ) );
457
458 return keys;
459 }
460
461
462 std::vector<ITEM_TYPE> GetRowReferences( int aRow ) const
463 {
464 wxCHECK( aRow >= 0 && aRow < (int) m_rows.size(), std::vector<ITEM_TYPE>() );
465 return m_rows[aRow].m_items;
466 }
467
468
474 void RevertRow( int aRow ) override
475 {
476 wxCHECK_RET( aRow >= 0 && aRow < static_cast<int>( m_rows.size() ), "Invalid Row Number" );
477
478 for( const ITEM_TYPE& item : m_rows[aRow].m_items )
479 {
480 for( const DATA_MODEL_COL& col : m_cols )
481 updateDataStoreItemFieldFromLive( item, col.m_fieldName );
482 }
483
484 m_edited = false;
485
486 for( const ITEM_TYPE& item : getAllItems() )
487 {
488 for( const DATA_MODEL_COL& col : m_cols )
489 {
490 if( fieldIsModified( item, col.m_fieldName ) )
491 {
492 m_edited = true;
493 return;
494 }
495 }
496 }
497 }
498
499
500 void ExpandRow( int aRow )
501 {
502 std::vector<DATA_MODEL_ROW<ITEM_TYPE>> children;
503
504 for( ITEM_TYPE& ref : m_rows[aRow].m_items )
505 {
506 bool matchFound = false;
507
508 // See if we already have a child group which this symbol fits into
509 for( DATA_MODEL_ROW<ITEM_TYPE>& child : children )
510 {
511 // group members are by definition all matching, so just check
512 // against the first member
513 if( unitMatch( ref, child.m_items[0] ) )
514 {
515 matchFound = true;
516 child.m_items.push_back( ref );
517 break;
518 }
519 }
520
521 if( !matchFound )
522 children.emplace_back( ref, ROW_STATE::EXPANDED_CHILD );
523 }
524
525 if( children.size() < 2 )
526 return;
527
528 std::sort( children.begin(), children.end(),
529 [this]( const DATA_MODEL_ROW<ITEM_TYPE>& lhs,
530 const DATA_MODEL_ROW<ITEM_TYPE>& rhs ) -> bool
531 {
532 return cmpRows( lhs, rhs, m_sortColumn, m_sortAscending );
533 } );
534
535 m_rows[aRow].m_state = ROW_STATE::EXPANDED_PARENT;
536 m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() );
537
538 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow + 1, children.size() );
539 GetView()->ProcessTableMessage( msg );
540 }
541
542
543 void CollapseRow( int aRow )
544 {
545 auto firstChild = m_rows.begin() + aRow + 1;
546 auto afterLastChild = firstChild;
547 int deleted = 0;
548
549 while( afterLastChild != m_rows.end() && afterLastChild->m_state == ROW_STATE::EXPANDED_CHILD )
550 {
551 deleted++;
552 afterLastChild++;
553 }
554
555 m_rows[aRow].m_state = ROW_STATE::COLLAPSED;
556 m_rows.erase( firstChild, afterLastChild );
557
558 wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted );
559 GetView()->ProcessTableMessage( msg );
560 }
561
562
563 void ExpandCollapseRow( int aRow ) override
564 {
565 if( m_rows[aRow].m_state == ROW_STATE::COLLAPSED )
566 ExpandRow( aRow );
567 else if( m_rows[aRow].m_state == ROW_STATE::EXPANDED_PARENT )
568 CollapseRow( aRow );
569 }
570
571
573 {
574 for( size_t i = 0; i < m_rows.size(); ++i )
575 {
576 if( m_rows[i].m_state == ROW_STATE::EXPANDED_PARENT )
577 {
578 CollapseRow( i );
579 m_rows[i].m_state = ROW_STATE::COLLAPSED_DURING_SORT;
580 }
581 }
582 }
583
584
586 {
587 for( size_t i = 0; i < m_rows.size(); ++i )
588 {
589 if( m_rows[i].m_state == ROW_STATE::COLLAPSED_DURING_SORT )
590 ExpandRow( i );
591 }
592 }
593
594 wxString GetGroupedValue( const DATA_MODEL_ROW<ITEM_TYPE>& aRow, int aCol,
595 const wxString& refDelimiter = wxT( ", " ),
596 const wxString& refRangeDelimiter = wxT( "-" ),
597 bool resolveVars = false, bool listMixedValues = false )
598 {
599 std::vector<ITEM_TYPE> items;
600 std::set<wxString> mixedValues;
601 wxString fieldValue;
602
603 for( const ITEM_TYPE& item : aRow.m_items )
604 {
605 if( ColIsItemIdentifier( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
606 {
607 items.push_back( item );
608 }
609 else // Other columns are either a single value or ROW_MULTI_ITEMS
610 {
611 wxString itemFieldValue;
612 getStoredFieldValue( item, m_cols[aCol].m_fieldName, itemFieldValue );
613
614 // Show the effective state when a sheet forces it on, but do not change
615 // the stored value so the symbol is never stamped on apply.
616 if( ColIsAttribute( aCol ) && attributeForcedOnBySheet( item, m_cols[aCol].m_fieldName ) )
617 itemFieldValue = wxS( "1" );
618
619 if( resolveVars )
620 {
621 if( ColIsAttribute( aCol ) )
622 {
623 itemFieldValue =
624 getAttributeResolvedValue( m_cols[aCol].m_fieldName, itemFieldValue == wxS( "1" ) );
625 }
626 // Computed fields (e.g. ${FOOTPRINT_LIBRARY}) can't have un-applied values.
627 // Resolve them against the item.
628 else if( ColIsComputed( aCol ) )
629 {
630 itemFieldValue = getFieldResolvedLiveValue( item, m_cols[aCol].m_fieldName );
631 }
632 // We have a field that contains both non-variable text and a variable
633 else if( IsGeneratedValue( itemFieldValue ) )
634 {
635 // Resolve variables in the un-applied value using the parent symbol and instance
636 // data.
637 itemFieldValue = resolveTextVars( item, itemFieldValue );
638 }
639 }
640
641 if( listMixedValues )
642 mixedValues.insert( itemFieldValue );
643 else if( &item == &aRow.m_items.front() )
644 fieldValue = itemFieldValue;
645 else if( fieldValue != itemFieldValue )
646 return INDETERMINATE_STATE;
647 }
648 }
649
650 if( listMixedValues )
651 {
652 fieldValue = wxEmptyString;
653
654 for( const wxString& value : mixedValues )
655 {
656 if( value.IsEmpty() )
657 continue;
658 else if( fieldValue.IsEmpty() )
659 fieldValue = value;
660 else
661 fieldValue += "," + value;
662 }
663 }
664
665 if( ColIsItemIdentifier( aCol ) || ColIsQuantity( aCol ) || ColIsItemNumber( aCol ) )
666 {
667 // Remove duplicates (other units of multi-unit parts)
668 std::sort( items.begin(), items.end(),
669 [this]( const ITEM_TYPE& lhs, const ITEM_TYPE& rhs ) -> bool
670 {
671 return cmpRowItems( lhs, rhs );
672 } );
673
674 auto logicalEnd = std::unique( items.begin(), items.end(),
675 [this]( const ITEM_TYPE& lhs, const ITEM_TYPE& rhs ) -> bool
676 {
677 return unitMatch( lhs, rhs );
678 } );
679
680 items.erase( logicalEnd, items.end() );
681 }
682
683 // References are the item-identifier column for schematic symbols and footprints and have
684 // special formatting for representing reference ranges. Library item identifiers are joined
685 // without reference-range formatting.
686 if( ColIsItemIdentifier( aCol ) )
687 {
688 std::vector<wxString> itemIdentifiers;
689
690 for( const ITEM_TYPE& item : items )
691 itemIdentifiers.push_back( getItemIdentifier( item ) );
692
693 // C1-15, C17 kind of formatting
694 if( ColIsReference( aCol ) )
695 {
696 fieldValue = UTIL::FormatRefDesRanges( itemIdentifiers, refDelimiter, refRangeDelimiter );
697 }
698 else
699 {
700 fieldValue.clear();
701
702 for( const wxString& itemIdentifier : itemIdentifiers )
703 {
704 if( !fieldValue.IsEmpty() )
705 fieldValue += refDelimiter;
706
707 fieldValue += itemIdentifier;
708 }
709 }
710 }
711 else if( ColIsQuantity( aCol ) )
712 fieldValue = wxString::Format( wxT( "%d" ), (int) items.size() );
713 else if( ColIsItemNumber( aCol ) && aRow.m_state != ROW_STATE::EXPANDED_CHILD )
714 fieldValue = wxString::Format( wxT( "%d" ), aRow.m_itemNumber );
715
716 return fieldValue;
717 }
718
719
720 wxString GetValue( int aRow, int aCol ) override
721 {
722 wxCHECK( aRow >= 0 && aRow < GetNumberRows(), wxEmptyString );
723 wxCHECK( aCol >= 0 && aCol < GetNumberCols(), wxEmptyString );
724
725 return GetGroupedValue( m_rows[aRow], aCol );
726 }
727
728 wxString GetResolvedValue( int aRow, int aCol ) override
729 {
730 return GetGroupedValue( m_rows[aRow], aCol, wxT( ", " ), wxT( "-" ), true, false );
731 }
732
733 wxString GetExportValue( int aRow, int aCol, const wxString& refDelimiter,
734 const wxString& refRangeDelimiter ) override
735 {
736 return GetGroupedValue( m_rows[aRow], aCol, refDelimiter, refRangeDelimiter, true, true );
737 }
738
739 bool IsCellReadOnly( int aRow, int aCol ) override
740 {
743 }
744
745
746protected:
747 bool MatchesFilter( const ITEM_TYPE& aItem, const wxString& aReference,
748 EDA_COMBINED_MATCHER& aMatcher )
749 {
750 if( m_filter.IsEmpty() )
751 return true;
752
753 // There is a slight difference between how the reference field is matched between
754 // 'reference' mode and all other modes that only applies to multiunit symbols in the
755 // symbol fields table.
756 //
757 // For compatibility, a filter like U1A will match a multiunit U1 that has a unit A
758 // in 'reference' mode, but not in any other mode. It won't match a C1A to a C1 that
759 // isn't mulitunit.
760 //
761 // In non-reference mode, because multiunit symbols are all grouped together,
762 // e.g. U1A and U1B are in the same row as U1, the filter will only match if the
763 // reference is exactly U1.
764 //
765 // None of this matters for the footprint fields table / other future tables.
767 return aMatcher.Find( aReference.Lower() );
768
769 for( size_t i = 0; i < m_cols.size(); ++i )
770 {
771 const DATA_MODEL_COL& col = m_cols[i];
772
774 continue;
775
776 wxString value;
777 getStoredFieldValue( aItem, col.m_fieldName, value );
778
779 // We want to match on things like DNP and Excluded from BOM when the
780 // checkbox is checked
781 if( ColIsAttribute( static_cast<int>( i ) ) )
782 {
783 bool effectiveValue = value == wxS( "1" ) || attributeForcedOnBySheet( aItem, col.m_fieldName );
784 value = getAttributeResolvedValue( col.m_fieldName, effectiveValue );
785 }
786 // Match computed fields on their resolved value rather than the stored variable.
787 else if( ColIsComputed( static_cast<int>( i ) ) )
788 {
789 value = getFieldResolvedLiveValue( aItem, col.m_fieldName );
790 }
791 // Same as above, but for field values that contain a mix of text and variables, e.g. "Value: ${VALUE}"
792 // The point is to match on what the user can see
793 else if( IsGeneratedValue( value ) )
794 {
795 value = resolveTextVars( aItem, value );
796 }
797
798 if( aMatcher.Find( value.Lower() ) )
799 return true;
800 }
801
802 return false;
803 }
804
805
811 virtual bool attributeForcedOnBySheet( const ITEM_TYPE& aItem, const wxString& aAttributeName ) const
812 {
813 return false;
814 }
815
816
818 {
819 if( !ColIsAttribute( aCol ) || aRow.m_items.empty() )
820 return false;
821
822 // Lock the cell only when every symbol in the row inherits it, so a mixed group
823 // stays editable and shows the indeterminate state.
824 for( const ITEM_TYPE& item : aRow.m_items )
825 {
826 if( !attributeForcedOnBySheet( item, m_cols[aCol].m_fieldName ) )
827 return false;
828 }
829
830 return true;
831 }
832
834 int aSortCol, bool aAscending )
835 {
836 // Empty rows always go to the bottom, whether ascending or descending
837 if( lhRow.m_items.empty() )
838 return false;
839 else if( rhRow.m_items.empty() )
840 return true;
841
842 // N.B. To meet the iterator sort conditions, we cannot simply invert the truth
843 // to get the opposite sort. i.e. ~(a<b) != (a>b)
844 auto local_cmp =
845 [aAscending]( const auto a, const auto b )
846 {
847 if( aAscending )
848 return a < b;
849 else
850 return a > b;
851 };
852
853 // Primary sort key is sortCol; secondary is always the item identifier.
854 if( aSortCol < 0 || aSortCol >= this->GetNumberCols() )
855 aSortCol = 0;
856
857 wxString lhs = this->GetGroupedValue( lhRow, aSortCol, wxT( ", " ), wxT( "-" ), true )
858 .Trim( true )
859 .Trim( false );
860 wxString rhs = this->GetGroupedValue( rhRow, aSortCol, wxT( ", " ), wxT( "-" ), true )
861 .Trim( true )
862 .Trim( false );
863
864 if( lhs == rhs || this->ColIsItemIdentifier( aSortCol ) )
865 {
866 if( aAscending )
867 return cmpRowItems( lhRow.m_items[0], rhRow.m_items[0] );
868 else
869 return cmpRowItems( rhRow.m_items[0], lhRow.m_items[0] );
870 }
871 else
872 {
873 return local_cmp( ValueStringCompare( lhs, rhs ), 0 );
874 }
875 }
876
877 // Used for sorting items that are grouped within a single row by their item identifiers.
878 virtual bool cmpRowItems( const ITEM_TYPE& lhItem, const ITEM_TYPE& rhItem )
879 {
880 return StrNumCmp( getItemIdentifier( lhItem ), getItemIdentifier( rhItem ), true ) < 0;
881 }
882
883 virtual bool unitMatch( const ITEM_TYPE& lhItem, const ITEM_TYPE& rhItem ) = 0;
884
885 bool groupMatch( const ITEM_TYPE& lhItem, const ITEM_TYPE& rhItem )
886 {
887 bool matchFound = false;
888
889 for( size_t i = 0; i < m_cols.size(); ++i )
890 {
891 if( !m_cols[i].m_group )
892 continue;
893
894 int col = static_cast<int>( i );
895
896 // Schematic and PCB references are item identifiers and group by reference prefix.
897 // Library item identifiers and library references use ordinary exact matching below.
898 if( ColIsItemIdentifier( col ) && ColIsReference( col ) )
899 {
902 {
903 return false;
904 }
905
906 matchFound = true;
907 continue;
908 }
909
910 const wxString& fieldName = m_cols[i].m_fieldName;
911
912 wxString lh;
913 wxString rh;
914 getStoredFieldValue( lhItem, fieldName, lh );
915 getStoredFieldValue( rhItem, fieldName, rh );
916
917 // Normalize attributes so absent and explicitly false values group together, while
918 // still honoring edits in the data store and effective values inherited from sheets.
919 if( ColIsAttribute( col ) )
920 {
921 // Sheets can force values like DNP on, but it can't force them off
922 bool lhEffectiveValue = lh == wxS( "1" ) || attributeForcedOnBySheet( lhItem, fieldName );
923 bool rhEffectiveValue = rh == wxS( "1" ) || attributeForcedOnBySheet( rhItem, fieldName );
924
925 if( lhEffectiveValue != rhEffectiveValue )
926 return false;
927
928 matchFound = true;
929 continue;
930 }
931 // Computed fields (e.g. ${QUANTITY}) are resolved through the item. Property-backed
932 // fields use the store so regrouping includes changes that haven't been applied yet.
933 else if( ColIsComputed( col ) )
934 {
935 lh = getFieldResolvedLiveValue( lhItem, fieldName );
936 rh = getFieldResolvedLiveValue( rhItem, fieldName );
937 }
938 // If we're not generated, we might still have a variable reference in the value,
939 // e.g. "10K ${TOLERANCE}", which still needs to be resolved
940 else
941 {
942 if( IsGeneratedValue( lh ) )
943 lh = resolveTextVars( lhItem, lh );
944 if( IsGeneratedValue( rh ) )
945 rh = resolveTextVars( rhItem, rh );
946 }
947
948 if( lh != rh )
949 return false;
950
951 matchFound = true;
952 }
953
954 return matchFound;
955 }
956
957 void Sort()
958 {
960
961 // We're going to sort the rows based on their first item, so the first item identifier had
962 // better be the lowest one.
964 {
965 std::sort( row.m_items.begin(), row.m_items.end(),
966 [this]( const ITEM_TYPE& lhs, const ITEM_TYPE& rhs ) -> bool
967 {
968 return cmpRowItems( lhs, rhs );
969 } );
970 }
971
972 std::sort( m_rows.begin(), m_rows.end(),
973 [this]( const DATA_MODEL_ROW<ITEM_TYPE>& lhs, const DATA_MODEL_ROW<ITEM_TYPE>& rhs ) -> bool
974 {
975 return cmpRows( lhs, rhs, m_sortColumn, m_sortAscending );
976 } );
977
978 // Time to renumber the item numbers
979 int itemNumber = 1;
980
982 {
983 row.m_itemNumber = itemNumber++;
984 }
985
987 }
988
989
996 virtual bool getLiveFieldValue( const ITEM_TYPE& aItem, const wxString& aFieldName, wxString& aValue ) = 0;
997
1001 const std::map<wxString, wxString>& getStoredFields( const ITEM_TYPE& aItem ) const
1002 {
1003 static const std::map<wxString, wxString> emptyFields;
1004
1005 auto itemIt = m_dataStore.find( getDataStoreKey( aItem ) );
1006
1007 if( itemIt == m_dataStore.end() )
1008 return emptyFields;
1009
1010 return itemIt->second;
1011 }
1012
1019 bool getStoredFieldValue( const ITEM_TYPE& aItem, const wxString& aFieldName, wxString& aValue ) const
1020 {
1021 aValue.clear();
1022
1023 const std::map<wxString, wxString>& fields = getStoredFields( aItem );
1024 auto fieldIt = fields.find( aFieldName );
1025
1026 if( fieldIt == fields.end() )
1027 return false;
1028
1029 aValue = fieldIt->second;
1030 return true;
1031 }
1032
1036 void setStoredFieldValue( const ITEM_TYPE& aItem, const wxString& aFieldName,
1037 const wxString& aValue )
1038 {
1039 m_dataStore[getDataStoreKey( aItem )][aFieldName] = aValue;
1040 }
1041
1046 void ensureStoredFieldPresent( const ITEM_TYPE& aItem, const wxString& aFieldName )
1047 {
1048 m_dataStore[getDataStoreKey( aItem )].try_emplace( aFieldName, wxEmptyString );
1049 }
1050
1054 void clearStoredField( const ITEM_TYPE& aItem, const wxString& aFieldName )
1055 {
1056 auto itemIt = m_dataStore.find( getDataStoreKey( aItem ) );
1057
1058 if( itemIt != m_dataStore.end() )
1059 itemIt->second.erase( aFieldName );
1060 }
1061
1067 bool fieldIsModified( const ITEM_TYPE& aItem, const wxString& aFieldName )
1068 {
1069 wxString liveValue;
1070 wxString storedValue;
1071 bool liveFieldPresent = getLiveFieldValue( aItem, aFieldName, liveValue );
1072 bool storedFieldPresent = getStoredFieldValue( aItem, aFieldName, storedValue );
1073
1074 return liveFieldPresent != storedFieldPresent || liveValue != storedValue;
1075 }
1076
1077
1084 void updateDataStoreItemFieldFromLive( const ITEM_TYPE& aItem, const wxString& aFieldName )
1085 {
1086 wxString value;
1087
1088 if( getLiveFieldValue( aItem, aFieldName, value ) )
1089 setStoredFieldValue( aItem, aFieldName, value );
1090 else
1091 clearStoredField( aItem, aFieldName );
1092 }
1093
1099 void initializeDataStoreItemField( const ITEM_TYPE& aItem, const DATA_MODEL_COL& aCol )
1100 {
1102
1103 if( aCol.m_userAdded )
1105 }
1106
1113 void initializeDataStoreItem( const ITEM_TYPE& aItem )
1114 {
1115 for( const DATA_MODEL_COL& col : m_cols )
1116 initializeDataStoreItemField( aItem, col );
1117 }
1118
1119 virtual std::vector<ITEM_TYPE> getAllItems() const = 0;
1120
1121public:
1122 virtual KIID_PATH getDataStoreKey( const ITEM_TYPE& aItem ) const = 0;
1123 virtual wxString getItemIdentifier( const ITEM_TYPE& aItem ) const = 0;
1124
1134 virtual wxString getFieldResolvedLiveValue( const ITEM_TYPE& aItem, const wxString& aFieldName ) = 0;
1135
1136 virtual wxString resolveTextVars( const ITEM_TYPE& aItem, const wxString& aText ) = 0;
1137
1138protected:
1139 std::vector<DATA_MODEL_ROW<ITEM_TYPE>> m_rows;
1140};
BOM_FILTER_SCOPE
bool Find(const wxString &aTerm, int &aMatchersTriggered, int &aPosition)
Look in all existing matchers, return the earliest match of any of the existing.
void SetSelectionItems(const std::unordered_set< KIID_PATH > &aItems)
bool cellUsesResolvedTextRenderer(int aRow, int aCol)
const wxString & GetCurrentVariant() const
virtual bool fieldIsItemProperty(const wxString &aFieldName) const
void SetVariantNames(const std::vector< wxString > &aVariantNames)
bool cellUsesUrlEditor(int aRow, int aCol)
BOM_FILTER_SCOPE GetFilterScope() const
virtual bool IsCellEdited(int aRow, int aCol)=0
void SetSorting(int aCol, bool aAscending)
void MoveColumn(int aCol, int aNewPos)
virtual void ClearCell(int aRow, int aCol)=0
bool IsEmptyCell(int aRow, int aCol) override
void SetShowColumn(int aCol, bool aShow)
void SetIncludeExcludedFromBOM(bool aInclude)
bool IsRowEdited(int aRow)
Return true if any cell in the row has been edited.
wxGridCellAttr * applyFieldPresenceRenderer(wxGridCellAttr *aAttr, int aRow, int aCol)
std::vector< BOM_FIELD > GetFieldsOrdered()
void applyResolvedTextRenderer(wxGridCellAttr *aAttr, bool aApplyTint)
static const wxString ITEM_NUMBER_VARIABLE
virtual bool ColIsReadOnly(int aCol) const
virtual void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser)=0
bool HasUndoStateSerialization() const override
Optional identity-based serialization for the host dialog's Ctrl+Z/Ctrl+Y.
wxString Export(const BOM_FMT_PRESET &aSettings)
wxGridCellRenderer * m_resolvedTextRenderer
virtual wxString GetExportValue(int aRow, int aCol, const wxString &aRefDelimiter, const wxString &aRefRangeDelimiter)=0
virtual bool ColIsItemIdentifier(int aCol) const
Reference for symbol/fields tables, lib_id for lib tables.
void SetColLabelValue(int aCol, const wxString &aLabel) override
void SetFilterScope(BOM_FILTER_SCOPE aScope)
virtual bool fieldIsAttribute(const wxString &aFieldName) const
wxGridCellAttr * applyCellDecorations(wxGridCellAttr *aAttr, int aRow, int aCol)
virtual wxString getAttributeResolvedValue(const wxString &aFieldName, bool aValue) const
void SetFieldsOrder(const std::vector< wxString > &aNewOrder)
virtual void RevertRow(int aRow)=0
virtual bool IsCellReadOnly(int aRow, int aCol)
int GetFieldNameCol(const wxString &aFieldName) const
virtual wxString GetResolvedValue(int aRow, int aCol)=0
wxString SerializeUndoState() const override
bool IsExpanderColumn(int aCol) const override
std::unordered_set< KIID_PATH > m_selectionItems
std::vector< wxString > m_variantNames
Variant names for multi-variant DNP filtering.
virtual void ExpandCollapseRow(int aRow)=0
std::vector< DATA_MODEL_COL > m_cols
const std::vector< wxString > & GetVariantNames() const
void RestoreUndoState(const wxString &aState) override
void SetCurrentVariant(const wxString &aVariantName)
Set the current variant name for highlighting purposes.
void RenameColumn(int aCol, const wxString &newName)
void SetGroupColumn(int aCol, bool aGroup)
void SetFilter(const wxString &aFilter)
wxString GetColLabelValue(int aCol) override
virtual std::vector< KIID_PATH > GetRowItemKeys(int aRow) const =0
Return the stable data-store keys of every item represented by a row.
void ApplyBomPreset(const BOM_PRESET &aPreset)
virtual void RebuildRows()=0
virtual bool IsCellClear(int aRow, int aCol)=0
wxString m_currentVariant
Current variant name for highlighting.
bool m_rebuildsEnabled
Items included by the user selection scope.
std::map< KIID_PATH, std::map< wxString, wxString > > m_dataStore
The rest of the generic fields data model stuff, primarily around row functionality.
void RevertRow(int aRow) override
Go through and revert all the fields in the row to their live values from the item (symbol/footprint/...
virtual wxString getItemIdentifier(const ITEM_TYPE &aItem) const =0
std::vector< ITEM_TYPE > GetRowReferences(int aRow) const
bool groupMatch(const ITEM_TYPE &lhItem, const ITEM_TYPE &rhItem)
void ensureStoredFieldPresent(const ITEM_TYPE &aItem, const wxString &aFieldName)
Makes sure a field is at least marked present-but-empty without changing its value if it has one alre...
void ExpandCollapseRow(int aRow) override
virtual std::vector< ITEM_TYPE > getAllItems() const =0
wxString GetExportValue(int aRow, int aCol, const wxString &refDelimiter, const wxString &refRangeDelimiter) override
void setStoredFieldValue(const ITEM_TYPE &aItem, const wxString &aFieldName, const wxString &aValue)
Creates or updates a field in the data store.
bool fieldIsModified(const ITEM_TYPE &aItem, const wxString &aFieldName)
Compares the live value of the field to the stored value in the data store.
const std::map< wxString, wxString > & getStoredFields(const ITEM_TYPE &aItem) const
Returns all stored fields for an item without creating a data-store entry.
void initializeDataStoreItemField(const ITEM_TYPE &aItem, const DATA_MODEL_COL &aCol)
Puts the live value of the field in the data store, correctly handling a missing field in the souce i...
void initializeDataStoreItem(const ITEM_TYPE &aItem)
Initializes every column in the data store for a newly added (or refreshed) item.
void clearStoredField(const ITEM_TYPE &aItem, const wxString &aFieldName)
Clears a field, e.g.
virtual bool unitMatch(const ITEM_TYPE &lhItem, const ITEM_TYPE &rhItem)=0
bool cmpRows(const DATA_MODEL_ROW< ITEM_TYPE > &lhRow, const DATA_MODEL_ROW< ITEM_TYPE > &rhRow, int aSortCol, bool aAscending)
bool getStoredFieldValue(const ITEM_TYPE &aItem, const wxString &aFieldName, wxString &aValue) const
Gets the stored value of the field from the data store, not from the live item, e....
virtual bool getLiveFieldValue(const ITEM_TYPE &aItem, const wxString &aFieldName, wxString &aValue)=0
Gets the current value of the field from the live item, e.g.
bool IsCellEdited(int aRow, int aCol) override
Returns true if the cell has been modified from the live value in any item in the row (symbol/footpri...
std::vector< DATA_MODEL_ROW< ITEM_TYPE > > m_rows
bool IsCellReadOnly(int aRow, int aCol) override
void AddColumn(const wxString &aFieldName, const wxString &aLabel, bool aAddedByUser) override
wxString GetValue(int aRow, int aCol) override
std::vector< KIID_PATH > GetRowItemKeys(int aRow) const override
Return the stable data-store keys of every item represented by a row.
bool MatchesFilter(const ITEM_TYPE &aItem, const wxString &aReference, EDA_COMBINED_MATCHER &aMatcher)
bool IsCellClear(int aRow, int aCol) override
Returns true if the cell is not present in the data store for any item in the row,...
bool allRowItemsHaveAttributeForcedOnBySheet(const DATA_MODEL_ROW< ITEM_TYPE > &aRow, int aCol)
wxString GetGroupedValue(const DATA_MODEL_ROW< ITEM_TYPE > &aRow, int aCol, const wxString &refDelimiter=wxT(", "), const wxString &refRangeDelimiter=wxT("-"), bool resolveVars=false, bool listMixedValues=false)
void updateDataStoreItemFieldFromLive(const ITEM_TYPE &aItem, const wxString &aFieldName)
Updates the data store with the current value of the field from the live item.
virtual KIID_PATH getDataStoreKey(const ITEM_TYPE &aItem) const =0
void ClearCell(int aRow, int aCol) override
Clears the field from the data store, rather than setting its value to an empty string.
ROW_STATE GetRowState(int aRow) const override
virtual bool attributeForcedOnBySheet(const ITEM_TYPE &aItem, const wxString &aAttributeName) const
Sheets can force all the symbols in them to have certain attributes on, like DNP.
wxString GetResolvedValue(int aRow, int aCol) override
virtual wxString getFieldResolvedLiveValue(const ITEM_TYPE &aItem, const wxString &aFieldName)=0
Explicitly bypasses the data store's field values and retries them from the item's current field valu...
virtual wxString resolveTextVars(const ITEM_TYPE &aItem, const wxString &aText)=0
virtual bool cmpRowItems(const ITEM_TYPE &lhItem, const ITEM_TYPE &rhItem)
wxSize GetBestSize(wxGrid &aGrid, wxGridCellAttr &aAttr, wxDC &aDC, int aRow, int aCol) override
wxGridCellRenderer * Clone() const override
void Draw(wxGrid &aGrid, wxGridCellAttr &aAttr, wxDC &aDC, const wxRect &aRect, int aRow, int aCol, bool isSelected) override
bool IsGeneratedValue(const wxString &aValue)
Returns true if some of the string is generated, e.g contains a text var or expression.
Definition common.cpp:502
Abstract pattern-matching tool and implementations.
const wxColour VARIANT_FIELD_OVERRIDE_DARK_YELLOW(80, 80, 40)
const wxColour VARIANT_FIELD_OVERRIDE_LIGHT_YELLOW(255, 255, 200)
const wxColour STRIPED_CLEARED_EMPTY_FIELD_LIGHT_GREEN(180, 220, 180)
const wxColour TEXT_VARIABLE_DARK_AMBER(80, 70, 30)
const wxColour CLEARED_FIELD_STRIPE_ON_DARK_LIGHT_RED(220, 180, 180)
const wxColour VARIANT_SYMBOL_OVERRIDE_LIGHT_BLUE(220, 235, 255)
const wxColour STRIPED_EDITED_EMPTY_FIELD_MUTED_GREEN(180, 200, 180)
const wxColour EDITED_EMPTY_FIELD_BRIGHT_GREEN(192, 255, 192)
const wxColour TEXT_VARIABLE_LIGHT_YELLOW(255, 252, 200)
const wxColour VARIANT_SYMBOL_OVERRIDE_DARK_BLUE(40, 60, 80)
const wxColour STRIPED_EDITED_NONEMPTY_FIELD_MUTED_RED(200, 180, 180)
const wxColour STRIPED_CLEARED_NONEMPTY_FIELD_LIGHT_RED(220, 180, 180)
const wxColour CLEARED_FIELD_STRIPE_ON_LIGHT_DARK_RED(100, 10, 10)
wxString GetRefDesPrefix(const wxString &aRefDes)
Get the (non-numeric) prefix from a refdes - e.g.
wxString FormatRefDesRanges(const std::vector< wxString > &aReferences, const wxString &aRefDelimiter, const wxString &aRefRangeDelimiter)
Format a sorted list of reference designators, shortening consecutive sequences of three or more refe...
Collection of utility functions for component reference designators (refdes)
int StrNumCmp(const wxString &aString1, const wxString &aString2, bool aIgnoreCase)
Compare two strings with alphanumerical content.
int ValueStringCompare(const wxString &strFWord, const wxString &strSWord)
Compare strings like the strcmp function but handle numbers and modifiers within the string text corr...
The point of the data model classes is fundamentally to represent three things:
std::vector< LIB_SYMBOL * > m_items
DATA_MODEL_ROW(const ITEM_TYPE &aFirstItem, ROW_STATE aGroupingState)
Functions to provide common constants and other functions to assist in making a consistent UI.
#define INDETERMINATE_STATE
Used for holding indeterminate values, such as with multiple selections holding different values or c...
Definition ui_common.h:46
enum KICOMMON_API ROW_STATE
Used primarily by the fields tables to collapse multiple grouped symbols, etc.
Definition wx_grid.h:44