From cccd7088601e3b85a0c963f618d4128844c0ee1f Mon Sep 17 00:00:00 2001 From: Mike Williams Date: Tue, 28 Feb 2023 13:37:04 -0500 Subject: [PATCH] Symbol Fields Table: move data model into its own file --- eeschema/CMakeLists.txt | 1 + .../dialogs/dialog_symbol_fields_table.cpp | 679 +----------------- eeschema/dialogs/dialog_symbol_fields_table.h | 2 + .../dialog_symbol_fields_table_base.cpp | 8 +- .../dialog_symbol_fields_table_base.fbp | 6 +- .../dialogs/dialog_symbol_fields_table_base.h | 1 + eeschema/fields_data_model.cpp | 563 +++++++++++++++ eeschema/fields_data_model.h | 149 ++++ 8 files changed, 732 insertions(+), 677 deletions(-) create mode 100644 eeschema/fields_data_model.cpp create mode 100644 eeschema/fields_data_model.h diff --git a/eeschema/CMakeLists.txt b/eeschema/CMakeLists.txt index 69ba870a1e..e764c1ab13 100644 --- a/eeschema/CMakeLists.txt +++ b/eeschema/CMakeLists.txt @@ -260,6 +260,7 @@ set( EESCHEMA_SRCS erc_item.cpp erc_sch_pin_context.cpp erc_settings.cpp + fields_data_model.cpp fields_grid_table.cpp files-io.cpp generate_alias_info.cpp diff --git a/eeschema/dialogs/dialog_symbol_fields_table.cpp b/eeschema/dialogs/dialog_symbol_fields_table.cpp index 2bad4e93b8..c050e5f030 100644 --- a/eeschema/dialogs/dialog_symbol_fields_table.cpp +++ b/eeschema/dialogs/dialog_symbol_fields_table.cpp @@ -49,17 +49,9 @@ #include #include #include "dialog_symbol_fields_table.h" +#include #include "eda_list_dialog.h" -// The field name in the data model (translated) -#define DISPLAY_NAME_COLUMN 0 -// The field name's label for exporting (CSV, etc.) -#define LABEL_COLUMN 1 -#define SHOW_FIELD_COLUMN 2 -#define GROUP_BY_COLUMN 3 -// The internal field name (untranslated) -#define FIELD_NAME_COLUMN 4 - #ifdef __WXMAC__ #define COLUMN_MARGIN 5 #else @@ -148,16 +140,6 @@ protected: }; -enum GROUP_TYPE -{ - GROUP_SINGLETON, - GROUP_COLLAPSED, - GROUP_COLLAPSED_DURING_SORT, - GROUP_EXPANDED, - CHILD_ITEM -}; - - BOM_PRESET DIALOG_SYMBOL_FIELDS_TABLE::bomPresetGroupedByValue( _HKI( "Grouped By Value" ), std::map( { @@ -198,657 +180,6 @@ BOM_PRESET DIALOG_SYMBOL_FIELDS_TABLE::bomPresetGroupedByValueFootprint( _HKI( "" ), true ); -struct DATA_MODEL_ROW -{ - DATA_MODEL_ROW( const SCH_REFERENCE& aFirstReference, GROUP_TYPE aType ) - { - m_Refs.push_back( aFirstReference ); - m_Flag = aType; - } - - GROUP_TYPE m_Flag; - std::vector m_Refs; -}; - - -struct DATA_MODEL_COL -{ - wxString m_fieldName; - wxString m_label; - bool m_userAdded; -}; - - -class FIELDS_EDITOR_GRID_DATA_MODEL : public wxGridTableBase -{ -protected: - // The data model is fundamentally m_componentRefs X m_fieldNames. - - SCH_EDIT_FRAME* m_frame; - SCH_REFERENCE_LIST m_symbolsList; - bool m_edited; - int m_sortColumn; - bool m_sortAscending; - std::vector m_cols; - - // However, the grid view can vary in two ways: - // 1) the componentRefs can be grouped into fewer rows - // 2) some columns can be hidden - // - // We handle (1) here (ie: a table row maps to a group, and the table is rebuilt - // when the groupings change), and we let the wxGrid handle (2) (ie: the number - // of columns is constant but are hidden/shown by the wxGrid control). - - std::vector< DATA_MODEL_ROW > m_rows; - - // Data store - // A map of compID : fieldSet, where fieldSet is a map of fieldName : fieldValue - std::map< KIID, std::map > m_dataStore; - -public: - FIELDS_EDITOR_GRID_DATA_MODEL( SCH_EDIT_FRAME* aFrame, SCH_REFERENCE_LIST& aSymbolsList ) : - m_frame( aFrame ), - m_symbolsList( aSymbolsList ), - m_edited( false ), - m_sortColumn( 0 ), - m_sortAscending( false ) - { - m_symbolsList.SplitReferences(); - } - - void AddColumn( const wxString& aFieldName, const wxString& aLabel, bool aAddedByUser ) - { - m_cols.push_back((struct DATA_MODEL_COL) { - .m_fieldName = aFieldName, - .m_label = aLabel, - .m_userAdded = aAddedByUser }); - - for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i ) - { - SCH_SYMBOL* symbol = m_symbolsList[ i ].GetSymbol(); - - wxCHECK( symbol && ( symbol->GetInstanceReferences().size() != 0 ), /* void */ ); - - wxString val = symbol->GetFieldText( aFieldName ); - - if( aFieldName == wxT( "Value" ) ) - val = symbol->GetValueFieldText( true ); - else if( aFieldName == wxT( "Footprint" ) ) - val = symbol->GetFootprintFieldText( true ); - - m_dataStore[ symbol->m_Uuid ][ aFieldName ] = val; - } - } - - void RemoveColumn( int aCol ) - { - for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i ) - { - SCH_SYMBOL* symbol = m_symbolsList[ i ].GetSymbol(); - m_dataStore[symbol->m_Uuid].erase( m_cols[aCol].m_fieldName ); - } - - m_cols.erase( m_cols.begin() + aCol ); - } - - void RenameColumn( int aCol, const wxString& newName ) - { - for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i ) - { - SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol(); - - auto node = m_dataStore[symbol->m_Uuid].extract( m_cols[aCol].m_fieldName ); - node.key() = newName; - m_dataStore[symbol->m_Uuid].insert( std::move( node ) ); - } - - m_cols[aCol].m_fieldName = newName; - } - - void MoveColumn( int aCol, int aNewPos ) { std::swap( m_cols[aCol], m_cols[aNewPos] ); } - - int GetNumberRows() override { return (int) m_rows.size(); } - - int GetNumberCols() override { return (int) m_cols.size(); } - - void SetColLabelValue( int aCol, const wxString& aLabel ) override - { - m_cols[aCol].m_label = aLabel; - } - - wxString GetColLabelValue( int aCol ) override { return m_cols[aCol].m_label; } - - wxString GetColFieldName( int aCol ) { return m_cols[aCol].m_fieldName; } - - int GetFieldNameCol( wxString aFieldName ) - { - for( size_t i = 0; i < m_cols.size(); i++ ) - { - if( m_cols[i].m_fieldName == aFieldName ) - return (int) i; - } - - return -1; - } - - const std::vector GetFieldsOrder() - { - std::vector fields; - - for( auto col : m_cols ) - { - fields.emplace_back( col.m_fieldName ); - } - - return fields; - } - - void SetFieldsOrder( const std::vector& aNewOrder ) - { - size_t foundCount = 0; - - for( const wxString& newField : aNewOrder ) - { - for( size_t i = 0; i < m_cols.size(); i++ ) - { - if( m_cols[i].m_fieldName == newField ) - { - std::swap( m_cols[foundCount], m_cols[i] ); - foundCount++; - } - } - } - } - - bool IsEmptyCell( int aRow, int aCol ) override - { - return false; // don't allow adjacent cell overflow, even if we are actually empty - } - - wxString GetValue( int aRow, int aCol ) override - { - if( ColIsReference( aCol ) ) - { - // Poor-man's tree controls - if( m_rows[ aRow ].m_Flag == GROUP_COLLAPSED ) - return wxT( "> " ) + GetValue( m_rows[ aRow ], aCol ); - else if (m_rows[ aRow ].m_Flag == GROUP_EXPANDED ) - return wxT( "v " ) + GetValue( m_rows[ aRow ], aCol ); - else if( m_rows[ aRow ].m_Flag == CHILD_ITEM ) - return wxT( " " ) + GetValue( m_rows[ aRow ], aCol ); - else - return wxT( " " ) + GetValue( m_rows[ aRow ], aCol ); - } - else - { - return GetValue( m_rows[ aRow ], aCol ); - } - } - - wxString GetRawValue( int aRow, int aCol ) - { - return GetValue( m_rows[ aRow ], aCol ); - } - - GROUP_TYPE GetRowFlags( int aRow ) - { - return m_rows[ aRow ].m_Flag; - } - - std::vector GetRowReferences( int aRow ) const - { - wxCHECK( aRow < (int)m_rows.size(), std::vector() ); - return m_rows[ aRow ].m_Refs; - } - - bool ColIsReference( int aCol ) - { - return ( aCol < (int) m_cols.size() ) && m_cols[aCol].m_fieldName == _( "Reference" ); - } - - bool ColIsQuantity( int aCol ) - { - return ( aCol < (int) m_cols.size() ) && m_cols[aCol].m_fieldName == _( "Qty" ); - } - - wxString GetValue( const DATA_MODEL_ROW& group, int aCol ) - { - std::vector references; - wxString fieldValue; - - for( const SCH_REFERENCE& ref : group.m_Refs ) - { - if( ColIsReference( aCol ) || ColIsQuantity( aCol ) ) - { - references.push_back( ref ); - } - else // Other columns are either a single value or ROW_MULTI_ITEMS - { - const KIID& symbolID = ref.GetSymbol()->m_Uuid; - - if( !m_dataStore.count( symbolID ) - || !m_dataStore[symbolID].count( m_cols[aCol].m_fieldName ) ) - { - return INDETERMINATE_STATE; - } - - if( &ref == &group.m_Refs.front() ) - fieldValue = m_dataStore[symbolID][m_cols[aCol].m_fieldName]; - else if( fieldValue != m_dataStore[symbolID][m_cols[aCol].m_fieldName] ) - return INDETERMINATE_STATE; - } - } - - if( ColIsReference( aCol ) || ColIsQuantity( aCol ) ) - { - // Remove duplicates (other units of multi-unit parts) - std::sort( references.begin(), references.end(), - []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool - { - wxString l_ref( l.GetRef() << l.GetRefNumber() ); - wxString r_ref( r.GetRef() << r.GetRefNumber() ); - return StrNumCmp( l_ref, r_ref, true ) < 0; - } ); - - auto logicalEnd = std::unique( references.begin(), references.end(), - []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool - { - // If unannotated then we can't tell what units belong together - // so we have to leave them all - if( l.GetRefNumber() == wxT( "?" ) ) - return false; - - wxString l_ref( l.GetRef() << l.GetRefNumber() ); - wxString r_ref( r.GetRef() << r.GetRefNumber() ); - return l_ref == r_ref; - } ); - - references.erase( logicalEnd, references.end() ); - } - - if( ColIsReference( aCol ) ) - fieldValue = SCH_REFERENCE_LIST::Shorthand( references ); - else if( ColIsQuantity( aCol ) ) - fieldValue = wxString::Format( wxT( "%d" ), ( int )references.size() ); - - return fieldValue; - } - - void SetValue( int aRow, int aCol, const wxString &aValue ) override - { - if( ColIsReference( aCol ) || ColIsQuantity( aCol ) ) - return; // Can't modify references or quantity - - DATA_MODEL_ROW& rowGroup = m_rows[aRow]; - - for( const SCH_REFERENCE& ref : rowGroup.m_Refs ) - m_dataStore[ref.GetSymbol()->m_Uuid][m_cols[aCol].m_fieldName] = aValue; - - m_edited = true; - } - - static bool cmp( const DATA_MODEL_ROW& lhGroup, const DATA_MODEL_ROW& rhGroup, - FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol, bool ascending ) - { - // Empty rows always go to the bottom, whether ascending or descending - if( lhGroup.m_Refs.size() == 0 ) - return true; - else if( rhGroup.m_Refs.size() == 0 ) - return false; - - // N.B. To meet the iterator sort conditions, we cannot simply invert the truth - // to get the opposite sort. i.e. ~(ab) - auto local_cmp = - [ ascending ]( const auto a, const auto b ) - { - if( ascending ) - return a < b; - else - return a > b; - }; - - // Primary sort key is sortCol; secondary is always REFERENCE (column 0) - - wxString lhs = dataModel->GetValue( (DATA_MODEL_ROW&) lhGroup, sortCol ); - wxString rhs = dataModel->GetValue( (DATA_MODEL_ROW&) rhGroup, sortCol ); - - if( lhs == rhs || sortCol == REFERENCE_FIELD ) - { - wxString lhRef = lhGroup.m_Refs[ 0 ].GetRef() + lhGroup.m_Refs[ 0 ].GetRefNumber(); - wxString rhRef = rhGroup.m_Refs[ 0 ].GetRef() + rhGroup.m_Refs[ 0 ].GetRefNumber(); - return local_cmp( StrNumCmp( lhRef, rhRef, true ), 0 ); - } - else - { - return local_cmp( ValueStringCompare( lhs, rhs ), 0 ); - } - } - - void Sort( int aColumn, bool ascending ) - { - if( aColumn < 0 ) - aColumn = 0; - - m_sortColumn = aColumn; - m_sortAscending = ascending; - - CollapseForSort(); - - // We're going to sort the rows based on their first reference, so the first reference - // had better be the lowest one. - for( DATA_MODEL_ROW& row : m_rows ) - { - std::sort( row.m_Refs.begin(), row.m_Refs.end(), - []( const SCH_REFERENCE& lhs, const SCH_REFERENCE& rhs ) - { - wxString lhs_ref( lhs.GetRef() << lhs.GetRefNumber() ); - wxString rhs_ref( rhs.GetRef() << rhs.GetRefNumber() ); - return StrNumCmp( lhs_ref, rhs_ref, true ) < 0; - } ); - } - - std::sort( m_rows.begin(), m_rows.end(), - [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool - { - return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending ); - } ); - - ExpandAfterSort(); - } - - bool unitMatch( const SCH_REFERENCE& lhRef, const SCH_REFERENCE& rhRef ) - { - // If items are unannotated then we can't tell if they're units of the same symbol or not - if( lhRef.GetRefNumber() == wxT( "?" ) ) - return false; - - return ( lhRef.GetRef() == rhRef.GetRef() && lhRef.GetRefNumber() == rhRef.GetRefNumber() ); - } - - bool groupMatch( const SCH_REFERENCE& lhRef, const SCH_REFERENCE& rhRef, - wxDataViewListCtrl* fieldsCtrl ) - { - bool matchFound = false; - - // First check the reference column. This can be done directly out of the - // SCH_REFERENCEs as the references can't be edited in the grid. - if( fieldsCtrl->GetToggleValue( REFERENCE_FIELD, GROUP_BY_COLUMN ) ) - { - // if we're grouping by reference, then only the prefix must match - if( lhRef.GetRef() != rhRef.GetRef() ) - return false; - - matchFound = true; - } - - const KIID& lhRefID = lhRef.GetSymbol()->m_Uuid; - const KIID& rhRefID = rhRef.GetSymbol()->m_Uuid; - - // Now check all the other columns. This must be done out of the dataStore - // for the refresh button to work after editing. - for( int i = REFERENCE_FIELD + 1; i < fieldsCtrl->GetItemCount(); ++i ) - { - if( !fieldsCtrl->GetToggleValue( i, GROUP_BY_COLUMN ) ) - continue; - - wxString fieldName = fieldsCtrl->GetTextValue( i, FIELD_NAME_COLUMN ); - - if( m_dataStore[ lhRefID ][ fieldName ] != m_dataStore[ rhRefID ][ fieldName ] ) - return false; - - matchFound = true; - } - - return matchFound; - } - - void RebuildRows( wxSearchCtrl* aFilter, wxCheckBox* aGroupSymbolsBox, - wxDataViewListCtrl* aFieldsCtrl ) - { - if( GetView() ) - { - // Commit any pending in-place edits before the row gets moved out from under - // the editor. - static_cast( GetView() )->CommitPendingChanges( true ); - - wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() ); - GetView()->ProcessTableMessage( msg ); - } - - m_rows.clear(); - - for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i ) - { - SCH_REFERENCE ref = m_symbolsList[ i ]; - - if( !aFilter->GetValue().IsEmpty() - && !WildCompareString( aFilter->GetValue(), ref.GetFullRef(), false ) ) - { - continue; - } - - bool matchFound = false; - - // See if we already have a row which this symbol fits into - for( DATA_MODEL_ROW& row : m_rows ) - { - // all group members must have identical refs so just use the first one - SCH_REFERENCE rowRef = row.m_Refs[ 0 ]; - - if( unitMatch( ref, rowRef ) ) - { - matchFound = true; - row.m_Refs.push_back( ref ); - break; - } - else if ( aGroupSymbolsBox->GetValue() && groupMatch( ref, rowRef, aFieldsCtrl ) ) - { - matchFound = true; - row.m_Refs.push_back( ref ); - row.m_Flag = GROUP_COLLAPSED; - break; - } - } - - if( !matchFound ) - m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) ); - } - - if ( GetView() ) - { - wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() ); - GetView()->ProcessTableMessage( msg ); - } - } - - void ExpandRow( int aRow ) - { - std::vector children; - - for( SCH_REFERENCE& ref : m_rows[ aRow ].m_Refs ) - { - bool matchFound = false; - - // See if we already have a child group which this symbol fits into - for( DATA_MODEL_ROW& child : children ) - { - // group members are by definition all matching, so just check - // against the first member - if( unitMatch( ref, child.m_Refs[ 0 ] ) ) - { - matchFound = true; - child.m_Refs.push_back( ref ); - break; - } - } - - if( !matchFound ) - children.emplace_back( DATA_MODEL_ROW( ref, CHILD_ITEM ) ); - } - - if( children.size() < 2 ) - return; - - std::sort( children.begin(), children.end(), - [ this ] ( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool - { - return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending ); - } ); - - m_rows[ aRow ].m_Flag = GROUP_EXPANDED; - m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() ); - - wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, children.size() ); - GetView()->ProcessTableMessage( msg ); - } - - void CollapseRow( int aRow ) - { - auto firstChild = m_rows.begin() + aRow + 1; - auto afterLastChild = firstChild; - int deleted = 0; - - while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM ) - { - deleted++; - afterLastChild++; - } - - m_rows[ aRow ].m_Flag = GROUP_COLLAPSED; - m_rows.erase( firstChild, afterLastChild ); - - wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted ); - GetView()->ProcessTableMessage( msg ); - } - - void ExpandCollapseRow( int aRow ) - { - DATA_MODEL_ROW& group = m_rows[ aRow ]; - - if( group.m_Flag == GROUP_COLLAPSED ) - ExpandRow( aRow ); - else if( group.m_Flag == GROUP_EXPANDED ) - CollapseRow( aRow ); - } - - void CollapseForSort() - { - for( size_t i = 0; i < m_rows.size(); ++i ) - { - if( m_rows[ i ].m_Flag == GROUP_EXPANDED ) - { - CollapseRow( i ); - m_rows[ i ].m_Flag = GROUP_COLLAPSED_DURING_SORT; - } - } - } - - void ExpandAfterSort() - { - for( size_t i = 0; i < m_rows.size(); ++i ) - { - if( m_rows[ i ].m_Flag == GROUP_COLLAPSED_DURING_SORT ) - ExpandRow( i ); - } - } - - void ApplyData() - { - for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i ) - { - SCH_SYMBOL& symbol = *m_symbolsList[ i ].GetSymbol(); - SCH_SCREEN* screen = m_symbolsList[i].GetSheetPath().LastScreen(); - - m_frame->SaveCopyInUndoList( screen, &symbol, UNDO_REDO::CHANGED, true ); - - const std::map& fieldStore = m_dataStore[symbol.m_Uuid]; - - for( const std::pair srcData : fieldStore ) - { - if( srcData.first == _( "Qty" ) ) - continue; - - const wxString& srcName = srcData.first; - const wxString& srcValue = srcData.second; - SCH_FIELD* destField = symbol.FindField( srcName ); - int col = GetFieldNameCol( srcName ); - bool userAdded = ( col != -1 && m_cols[col].m_userAdded ); - - // Add a not existing field if it has a value for this symbol - bool createField = !destField && ( !srcValue.IsEmpty() || userAdded ); - - if( createField ) - { - const VECTOR2I symbolPos = symbol.GetPosition(); - destField = symbol.AddField( SCH_FIELD( symbolPos, -1, &symbol, srcName ) ); - } - - if( !destField ) - continue; - - if( destField->GetId() == REFERENCE_FIELD ) - { - // Reference is not editable from this dialog - } - else if( destField->GetId() == VALUE_FIELD ) - { - // Value field cannot be empty - if( !srcValue.IsEmpty() ) - symbol.SetValueFieldText( srcValue ); - } - else if( destField->GetId() == FOOTPRINT_FIELD ) - { - symbol.SetFootprintFieldText( srcValue ); - } - else - { - destField->SetText( srcValue ); - } - } - - for( int ii = symbol.GetFields().size() - 1; ii >= MANDATORY_FIELDS; ii-- ) - { - if( fieldStore.count( symbol.GetFields()[ii].GetName() ) == 0 ) - symbol.GetFields().erase( symbol.GetFields().begin() + ii ); - } - } - - m_edited = false; - } - - int GetDataWidth( int aCol ) - { - int width = 0; - - if( ColIsReference( aCol ) ) - { - for( int row = 0; row < GetNumberRows(); ++row ) - width = std::max( width, KIUI::GetTextSize( GetValue( row, aCol ), GetView() ).x ); - } - else - { - wxString fieldName = GetColFieldName( aCol ); // symbol fieldName or Qty string - - for( unsigned symbolRef = 0; symbolRef < m_symbolsList.GetCount(); ++ symbolRef ) - { - const KIID& symbolID = m_symbolsList[ symbolRef ].GetSymbol()->m_Uuid; - wxString text = m_dataStore[symbolID][fieldName]; - - width = std::max( width, KIUI::GetTextSize( text, GetView() ).x ); - } - } - - return width; - } - - - bool IsEdited() - { - return m_edited; - } -}; - - DIALOG_SYMBOL_FIELDS_TABLE::DIALOG_SYMBOL_FIELDS_TABLE( SCH_EDIT_FRAME* parent ) : DIALOG_SYMBOL_FIELDS_TABLE_BASE( parent ), m_currentBomPreset( nullptr ), m_lastSelectedBomPreset( nullptr ), m_parent( parent ), @@ -1684,6 +1015,14 @@ void DIALOG_SYMBOL_FIELDS_TABLE::OnSaveAndContinue( wxCommandEvent& aEvent ) } +void DIALOG_SYMBOL_FIELDS_TABLE::OnPreviewRefresh( wxCommandEvent& event ) +{ + + + +} + + void DIALOG_SYMBOL_FIELDS_TABLE::OnExport( wxCommandEvent& aEvent ) { int last_col = m_grid->GetNumberCols() - 1; diff --git a/eeschema/dialogs/dialog_symbol_fields_table.h b/eeschema/dialogs/dialog_symbol_fields_table.h index c0fb531d77..3fe9768907 100644 --- a/eeschema/dialogs/dialog_symbol_fields_table.h +++ b/eeschema/dialogs/dialog_symbol_fields_table.h @@ -81,6 +81,8 @@ private: void OnFieldsCtrlSelectionChanged( wxDataViewEvent& event ) override; bool TryBefore( wxEvent& aEvent ) override; + void OnPreviewRefresh( wxCommandEvent& event ) override; + std::vector GetUserBomPresets() const; void SetUserBomPresets( std::vector& aPresetList ); void ApplyBomPreset( const wxString& aPresetName ); diff --git a/eeschema/dialogs/dialog_symbol_fields_table_base.cpp b/eeschema/dialogs/dialog_symbol_fields_table_base.cpp index baf5d39034..b3e6569b52 100644 --- a/eeschema/dialogs/dialog_symbol_fields_table_base.cpp +++ b/eeschema/dialogs/dialog_symbol_fields_table_base.cpp @@ -150,7 +150,7 @@ DIALOG_SYMBOL_FIELDS_TABLE_BASE::DIALOG_SYMBOL_FIELDS_TABLE_BASE( wxWindow* pare m_panelEdit->SetSizer( bEditSizer ); m_panelEdit->Layout(); bEditSizer->Fit( m_panelEdit ); - m_notebook1->AddPage( m_panelEdit, _("Edit"), true ); + m_notebook1->AddPage( m_panelEdit, _("Edit"), false ); m_panelExport = new wxPanel( m_notebook1, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL ); wxGridBagSizer* gbExport; gbExport = new wxGridBagSizer( 0, 0 ); @@ -252,7 +252,7 @@ DIALOG_SYMBOL_FIELDS_TABLE_BASE::DIALOG_SYMBOL_FIELDS_TABLE_BASE( wxWindow* pare m_panelExport->SetSizer( gbExport ); m_panelExport->Layout(); gbExport->Fit( m_panelExport ); - m_notebook1->AddPage( m_panelExport, _("Export"), false ); + m_notebook1->AddPage( m_panelExport, _("Export"), true ); bMainSizer->Add( m_notebook1, 1, wxEXPAND | wxALL, 5 ); @@ -306,7 +306,7 @@ DIALOG_SYMBOL_FIELDS_TABLE_BASE::DIALOG_SYMBOL_FIELDS_TABLE_BASE( wxWindow* pare m_grid->Connect( wxEVT_GRID_COL_SIZE, wxGridSizeEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnTableColSize ), NULL, this ); m_grid->Connect( wxEVT_GRID_RANGE_SELECT, wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnTableRangeSelected ), NULL, this ); m_browseButton->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnRegroupSymbols ), NULL, this ); - m_bRefreshPreview->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnRegroupSymbols ), NULL, this ); + m_bRefreshPreview->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnPreviewRefresh ), NULL, this ); m_buttonExport->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnExport ), NULL, this ); m_buttonApply->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnSaveAndContinue ), NULL, this ); m_sdbSizerCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnCancel ), NULL, this ); @@ -333,7 +333,7 @@ DIALOG_SYMBOL_FIELDS_TABLE_BASE::~DIALOG_SYMBOL_FIELDS_TABLE_BASE() m_grid->Disconnect( wxEVT_GRID_COL_SIZE, wxGridSizeEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnTableColSize ), NULL, this ); m_grid->Disconnect( wxEVT_GRID_RANGE_SELECT, wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnTableRangeSelected ), NULL, this ); m_browseButton->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnRegroupSymbols ), NULL, this ); - m_bRefreshPreview->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnRegroupSymbols ), NULL, this ); + m_bRefreshPreview->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnPreviewRefresh ), NULL, this ); m_buttonExport->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnExport ), NULL, this ); m_buttonApply->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnSaveAndContinue ), NULL, this ); m_sdbSizerCancel->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( DIALOG_SYMBOL_FIELDS_TABLE_BASE::OnCancel ), NULL, this ); diff --git a/eeschema/dialogs/dialog_symbol_fields_table_base.fbp b/eeschema/dialogs/dialog_symbol_fields_table_base.fbp index e31da85138..26ecc86c16 100644 --- a/eeschema/dialogs/dialog_symbol_fields_table_base.fbp +++ b/eeschema/dialogs/dialog_symbol_fields_table_base.fbp @@ -122,7 +122,7 @@ Edit - 1 + 0 1 1 @@ -1224,7 +1224,7 @@ Export - 0 + 1 1 1 @@ -2175,7 +2175,7 @@ - OnRegroupSymbols + OnPreviewRefresh diff --git a/eeschema/dialogs/dialog_symbol_fields_table_base.h b/eeschema/dialogs/dialog_symbol_fields_table_base.h index 9d439a87c1..0e4bdf750a 100644 --- a/eeschema/dialogs/dialog_symbol_fields_table_base.h +++ b/eeschema/dialogs/dialog_symbol_fields_table_base.h @@ -105,6 +105,7 @@ class DIALOG_SYMBOL_FIELDS_TABLE_BASE : public DIALOG_SHIM virtual void OnTableItemContextMenu( wxGridEvent& event ) { event.Skip(); } virtual void OnTableColSize( wxGridSizeEvent& event ) { event.Skip(); } virtual void OnTableRangeSelected( wxGridRangeSelectEvent& event ) { event.Skip(); } + virtual void OnPreviewRefresh( wxCommandEvent& event ) { event.Skip(); } virtual void OnExport( wxCommandEvent& event ) { event.Skip(); } virtual void OnSaveAndContinue( wxCommandEvent& event ) { event.Skip(); } virtual void OnCancel( wxCommandEvent& event ) { event.Skip(); } diff --git a/eeschema/fields_data_model.cpp b/eeschema/fields_data_model.cpp new file mode 100644 index 0000000000..3a03655950 --- /dev/null +++ b/eeschema/fields_data_model.cpp @@ -0,0 +1,563 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "string_utils.h" + +#include "fields_data_model.h" + +void FIELDS_EDITOR_GRID_DATA_MODEL::AddColumn( const wxString& aFieldName, const wxString& aLabel, + bool aAddedByUser ) +{ + m_cols.push_back((struct DATA_MODEL_COL) { + .m_fieldName = aFieldName, + .m_label = aLabel, + .m_userAdded = aAddedByUser }); + + for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i ) + { + SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol(); + + wxCHECK( symbol && ( symbol->GetInstanceReferences().size() != 0 ), /* void */ ); + + wxString val = symbol->GetFieldText( aFieldName ); + + if( aFieldName == wxT( "Value" ) ) + val = symbol->GetValueFieldText( true ); + else if( aFieldName == wxT( "Footprint" ) ) + val = symbol->GetFootprintFieldText( true ); + + m_dataStore[symbol->m_Uuid][aFieldName] = val; + } +} + +void FIELDS_EDITOR_GRID_DATA_MODEL::RemoveColumn( int aCol ) +{ + for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i ) + { + SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol(); + m_dataStore[symbol->m_Uuid].erase( m_cols[aCol].m_fieldName ); + } + + m_cols.erase( m_cols.begin() + aCol ); +} + +void FIELDS_EDITOR_GRID_DATA_MODEL::RenameColumn( int aCol, const wxString& newName ) +{ + for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i ) + { + SCH_SYMBOL* symbol = m_symbolsList[i].GetSymbol(); + + auto node = m_dataStore[symbol->m_Uuid].extract( m_cols[aCol].m_fieldName ); + node.key() = newName; + m_dataStore[symbol->m_Uuid].insert( std::move( node ) ); + } + + m_cols[aCol].m_fieldName = newName; +} + + +int FIELDS_EDITOR_GRID_DATA_MODEL::GetFieldNameCol( wxString aFieldName ) +{ + for( size_t i = 0; i < m_cols.size(); i++ ) + { + if( m_cols[i].m_fieldName == aFieldName ) + return (int) i; + } + + return -1; +} + + +const std::vector FIELDS_EDITOR_GRID_DATA_MODEL::GetFieldsOrder() +{ + std::vector fields; + + for( auto col : m_cols ) + { + fields.emplace_back( col.m_fieldName ); + } + + return fields; +} + +void FIELDS_EDITOR_GRID_DATA_MODEL::SetFieldsOrder( const std::vector& aNewOrder ) +{ + size_t foundCount = 0; + + for( const wxString& newField : aNewOrder ) + { + for( size_t i = 0; i < m_cols.size(); i++ ) + { + if( m_cols[i].m_fieldName == newField ) + { + std::swap( m_cols[foundCount], m_cols[i] ); + foundCount++; + } + } + } +} + + +wxString FIELDS_EDITOR_GRID_DATA_MODEL::GetValue( int aRow, int aCol ) +{ + if( ColIsReference( aCol ) ) + { + // Poor-man's tree controls + if( m_rows[aRow].m_Flag == GROUP_COLLAPSED ) + return wxT( "> " ) + GetValue( m_rows[aRow], aCol ); + else if( m_rows[aRow].m_Flag == GROUP_EXPANDED ) + return wxT( "v " ) + GetValue( m_rows[aRow], aCol ); + else if( m_rows[aRow].m_Flag == CHILD_ITEM ) + return wxT( " " ) + GetValue( m_rows[aRow], aCol ); + else + return wxT( " " ) + GetValue( m_rows[aRow], aCol ); + } + else + { + return GetValue( m_rows[aRow], aCol ); + } +} + +wxString FIELDS_EDITOR_GRID_DATA_MODEL::GetValue( const DATA_MODEL_ROW& group, int aCol ) +{ + std::vector references; + wxString fieldValue; + + for( const SCH_REFERENCE& ref : group.m_Refs ) + { + if( ColIsReference( aCol ) || ColIsQuantity( aCol ) ) + { + references.push_back( ref ); + } + else // Other columns are either a single value or ROW_MULTI_ITEMS + { + const KIID& symbolID = ref.GetSymbol()->m_Uuid; + + if( !m_dataStore.count( symbolID ) + || !m_dataStore[symbolID].count( m_cols[aCol].m_fieldName ) ) + { + return INDETERMINATE_STATE; + } + + if( &ref == &group.m_Refs.front() ) + fieldValue = m_dataStore[symbolID][m_cols[aCol].m_fieldName]; + else if( fieldValue != m_dataStore[symbolID][m_cols[aCol].m_fieldName] ) + return INDETERMINATE_STATE; + } + } + + if( ColIsReference( aCol ) || ColIsQuantity( aCol ) ) + { + // Remove duplicates (other units of multi-unit parts) + std::sort( references.begin(), references.end(), + []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool + { + wxString l_ref( l.GetRef() << l.GetRefNumber() ); + wxString r_ref( r.GetRef() << r.GetRefNumber() ); + return StrNumCmp( l_ref, r_ref, true ) < 0; + } ); + + auto logicalEnd = std::unique( references.begin(), references.end(), + []( const SCH_REFERENCE& l, const SCH_REFERENCE& r ) -> bool + { + // If unannotated then we can't tell what units belong together + // so we have to leave them all + if( l.GetRefNumber() == wxT( "?" ) ) + return false; + + wxString l_ref( l.GetRef() << l.GetRefNumber() ); + wxString r_ref( r.GetRef() << r.GetRefNumber() ); + return l_ref == r_ref; + } ); + + references.erase( logicalEnd, references.end() ); + } + + if( ColIsReference( aCol ) ) + fieldValue = SCH_REFERENCE_LIST::Shorthand( references ); + else if( ColIsQuantity( aCol ) ) + fieldValue = wxString::Format( wxT( "%d" ), (int) references.size() ); + + return fieldValue; +} + +void FIELDS_EDITOR_GRID_DATA_MODEL::SetValue( int aRow, int aCol, const wxString& aValue ) +{ + if( ColIsReference( aCol ) || ColIsQuantity( aCol ) ) + return; // Can't modify references or quantity + + DATA_MODEL_ROW& rowGroup = m_rows[aRow]; + + for( const SCH_REFERENCE& ref : rowGroup.m_Refs ) + m_dataStore[ref.GetSymbol()->m_Uuid][m_cols[aCol].m_fieldName] = aValue; + + m_edited = true; +} + +bool FIELDS_EDITOR_GRID_DATA_MODEL::cmp( const DATA_MODEL_ROW& lhGroup, + const DATA_MODEL_ROW& rhGroup, + FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol, + bool ascending ) +{ + // Empty rows always go to the bottom, whether ascending or descending + if( lhGroup.m_Refs.size() == 0 ) + return true; + else if( rhGroup.m_Refs.size() == 0 ) + return false; + + // N.B. To meet the iterator sort conditions, we cannot simply invert the truth + // to get the opposite sort. i.e. ~(ab) + auto local_cmp = + [ ascending ]( const auto a, const auto b ) + { + if( ascending ) + return a < b; + else + return a > b; + }; + + // Primary sort key is sortCol; secondary is always REFERENCE (column 0) + + wxString lhs = dataModel->GetValue( (DATA_MODEL_ROW&) lhGroup, sortCol ); + wxString rhs = dataModel->GetValue( (DATA_MODEL_ROW&) rhGroup, sortCol ); + + if( lhs == rhs || sortCol == REFERENCE_FIELD ) + { + wxString lhRef = lhGroup.m_Refs[0].GetRef() + lhGroup.m_Refs[0].GetRefNumber(); + wxString rhRef = rhGroup.m_Refs[0].GetRef() + rhGroup.m_Refs[0].GetRefNumber(); + return local_cmp( StrNumCmp( lhRef, rhRef, true ), 0 ); + } + else + { + return local_cmp( ValueStringCompare( lhs, rhs ), 0 ); + } +} + +void FIELDS_EDITOR_GRID_DATA_MODEL::Sort( int aColumn, bool ascending ) +{ + if( aColumn < 0 ) + aColumn = 0; + + m_sortColumn = aColumn; + m_sortAscending = ascending; + + CollapseForSort(); + + // We're going to sort the rows based on their first reference, so the first reference + // had better be the lowest one. + for( DATA_MODEL_ROW& row : m_rows ) + { + std::sort( row.m_Refs.begin(), row.m_Refs.end(), + []( const SCH_REFERENCE& lhs, const SCH_REFERENCE& rhs ) + { + wxString lhs_ref( lhs.GetRef() << lhs.GetRefNumber() ); + wxString rhs_ref( rhs.GetRef() << rhs.GetRefNumber() ); + return StrNumCmp( lhs_ref, rhs_ref, true ) < 0; + } ); + } + + std::sort( m_rows.begin(), m_rows.end(), + [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool + { + return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending ); + } ); + + ExpandAfterSort(); +} + +bool FIELDS_EDITOR_GRID_DATA_MODEL::unitMatch( const SCH_REFERENCE& lhRef, + const SCH_REFERENCE& rhRef ) +{ + // If items are unannotated then we can't tell if they're units of the same symbol or not + if( lhRef.GetRefNumber() == wxT( "?" ) ) + return false; + + return ( lhRef.GetRef() == rhRef.GetRef() && lhRef.GetRefNumber() == rhRef.GetRefNumber() ); +} + +bool FIELDS_EDITOR_GRID_DATA_MODEL::groupMatch( const SCH_REFERENCE& lhRef, + const SCH_REFERENCE& rhRef, + wxDataViewListCtrl* fieldsCtrl ) +{ + bool matchFound = false; + + // First check the reference column. This can be done directly out of the + // SCH_REFERENCEs as the references can't be edited in the grid. + if( fieldsCtrl->GetToggleValue( REFERENCE_FIELD, GROUP_BY_COLUMN ) ) + { + // if we're grouping by reference, then only the prefix must match + if( lhRef.GetRef() != rhRef.GetRef() ) + return false; + + matchFound = true; + } + + const KIID& lhRefID = lhRef.GetSymbol()->m_Uuid; + const KIID& rhRefID = rhRef.GetSymbol()->m_Uuid; + + // Now check all the other columns. This must be done out of the dataStore + // for the refresh button to work after editing. + for( int i = REFERENCE_FIELD + 1; i < fieldsCtrl->GetItemCount(); ++i ) + { + if( !fieldsCtrl->GetToggleValue( i, GROUP_BY_COLUMN ) ) + continue; + + wxString fieldName = fieldsCtrl->GetTextValue( i, FIELD_NAME_COLUMN ); + + if( m_dataStore[lhRefID][fieldName] != m_dataStore[rhRefID][fieldName] ) + return false; + + matchFound = true; + } + + return matchFound; +} + +void FIELDS_EDITOR_GRID_DATA_MODEL::RebuildRows( wxSearchCtrl* aFilter, + wxCheckBox* aGroupSymbolsBox, + wxDataViewListCtrl* aFieldsCtrl ) +{ + if( GetView() ) + { + // Commit any pending in-place edits before the row gets moved out from under + // the editor. + static_cast( GetView() )->CommitPendingChanges( true ); + + wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, 0, m_rows.size() ); + GetView()->ProcessTableMessage( msg ); + } + + m_rows.clear(); + + for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i ) + { + SCH_REFERENCE ref = m_symbolsList[i]; + + if( !aFilter->GetValue().IsEmpty() + && !WildCompareString( aFilter->GetValue(), ref.GetFullRef(), false ) ) + { + continue; + } + + bool matchFound = false; + + // See if we already have a row which this symbol fits into + for( DATA_MODEL_ROW& row : m_rows ) + { + // all group members must have identical refs so just use the first one + SCH_REFERENCE rowRef = row.m_Refs[0]; + + if( unitMatch( ref, rowRef ) ) + { + matchFound = true; + row.m_Refs.push_back( ref ); + break; + } + else if( aGroupSymbolsBox->GetValue() && groupMatch( ref, rowRef, aFieldsCtrl ) ) + { + matchFound = true; + row.m_Refs.push_back( ref ); + row.m_Flag = GROUP_COLLAPSED; + break; + } + } + + if( !matchFound ) + m_rows.emplace_back( DATA_MODEL_ROW( ref, GROUP_SINGLETON ) ); + } + + if( GetView() ) + { + wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_APPENDED, m_rows.size() ); + GetView()->ProcessTableMessage( msg ); + } +} + +void FIELDS_EDITOR_GRID_DATA_MODEL::ExpandRow( int aRow ) +{ + std::vector children; + + for( SCH_REFERENCE& ref : m_rows[aRow].m_Refs ) + { + bool matchFound = false; + + // See if we already have a child group which this symbol fits into + for( DATA_MODEL_ROW& child : children ) + { + // group members are by definition all matching, so just check + // against the first member + if( unitMatch( ref, child.m_Refs[0] ) ) + { + matchFound = true; + child.m_Refs.push_back( ref ); + break; + } + } + + if( !matchFound ) + children.emplace_back( DATA_MODEL_ROW( ref, CHILD_ITEM ) ); + } + + if( children.size() < 2 ) + return; + + std::sort( children.begin(), children.end(), + [this]( const DATA_MODEL_ROW& lhs, const DATA_MODEL_ROW& rhs ) -> bool + { + return cmp( lhs, rhs, this, m_sortColumn, m_sortAscending ); + } ); + + m_rows[aRow].m_Flag = GROUP_EXPANDED; + m_rows.insert( m_rows.begin() + aRow + 1, children.begin(), children.end() ); + + wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, aRow, children.size() ); + GetView()->ProcessTableMessage( msg ); +} + +void FIELDS_EDITOR_GRID_DATA_MODEL::CollapseRow( int aRow ) +{ + auto firstChild = m_rows.begin() + aRow + 1; + auto afterLastChild = firstChild; + int deleted = 0; + + while( afterLastChild != m_rows.end() && afterLastChild->m_Flag == CHILD_ITEM ) + { + deleted++; + afterLastChild++; + } + + m_rows[aRow].m_Flag = GROUP_COLLAPSED; + m_rows.erase( firstChild, afterLastChild ); + + wxGridTableMessage msg( this, wxGRIDTABLE_NOTIFY_ROWS_DELETED, aRow + 1, deleted ); + GetView()->ProcessTableMessage( msg ); +} + + +void FIELDS_EDITOR_GRID_DATA_MODEL::ExpandCollapseRow( int aRow ) +{ + DATA_MODEL_ROW& group = m_rows[aRow]; + + if( group.m_Flag == GROUP_COLLAPSED ) + ExpandRow( aRow ); + else if( group.m_Flag == GROUP_EXPANDED ) + CollapseRow( aRow ); +} + + +void FIELDS_EDITOR_GRID_DATA_MODEL::CollapseForSort() +{ + for( size_t i = 0; i < m_rows.size(); ++i ) + { + if( m_rows[i].m_Flag == GROUP_EXPANDED ) + { + CollapseRow( i ); + m_rows[i].m_Flag = GROUP_COLLAPSED_DURING_SORT; + } + } +} + + +void FIELDS_EDITOR_GRID_DATA_MODEL::ExpandAfterSort() +{ + for( size_t i = 0; i < m_rows.size(); ++i ) + { + if( m_rows[i].m_Flag == GROUP_COLLAPSED_DURING_SORT ) + ExpandRow( i ); + } +} + + +void FIELDS_EDITOR_GRID_DATA_MODEL::ApplyData() +{ + for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i ) + { + SCH_SYMBOL& symbol = *m_symbolsList[i].GetSymbol(); + SCH_SCREEN* screen = m_symbolsList[i].GetSheetPath().LastScreen(); + + m_frame->SaveCopyInUndoList( screen, &symbol, UNDO_REDO::CHANGED, true ); + + const std::map& fieldStore = m_dataStore[symbol.m_Uuid]; + + for( const std::pair srcData : fieldStore ) + { + if( srcData.first == _( "Qty" ) ) + continue; + + const wxString& srcName = srcData.first; + const wxString& srcValue = srcData.second; + SCH_FIELD* destField = symbol.FindField( srcName ); + int col = GetFieldNameCol( srcName ); + bool userAdded = ( col != -1 && m_cols[col].m_userAdded ); + + // Add a not existing field if it has a value for this symbol + bool createField = !destField && ( !srcValue.IsEmpty() || userAdded ); + + if( createField ) + { + const VECTOR2I symbolPos = symbol.GetPosition(); + destField = symbol.AddField( SCH_FIELD( symbolPos, -1, &symbol, srcName ) ); + } + + if( !destField ) + continue; + + if( destField->GetId() == REFERENCE_FIELD ) + { + // Reference is not editable from this dialog + } + else if( destField->GetId() == VALUE_FIELD ) + { + // Value field cannot be empty + if( !srcValue.IsEmpty() ) + symbol.SetValueFieldText( srcValue ); + } + else if( destField->GetId() == FOOTPRINT_FIELD ) + { + symbol.SetFootprintFieldText( srcValue ); + } + else + { + destField->SetText( srcValue ); + } + } + + for( int ii = symbol.GetFields().size() - 1; ii >= MANDATORY_FIELDS; ii-- ) + { + if( fieldStore.count( symbol.GetFields()[ii].GetName() ) == 0 ) + symbol.GetFields().erase( symbol.GetFields().begin() + ii ); + } + } + + m_edited = false; +} + +int FIELDS_EDITOR_GRID_DATA_MODEL::GetDataWidth( int aCol ) +{ + int width = 0; + + if( ColIsReference( aCol ) ) + { + for( int row = 0; row < GetNumberRows(); ++row ) + width = std::max( width, KIUI::GetTextSize( GetValue( row, aCol ), GetView() ).x ); + } + else + { + wxString fieldName = GetColFieldName( aCol ); // symbol fieldName or Qty string + + for( unsigned symbolRef = 0; symbolRef < m_symbolsList.GetCount(); ++symbolRef ) + { + const KIID& symbolID = m_symbolsList[symbolRef].GetSymbol()->m_Uuid; + wxString text = m_dataStore[symbolID][fieldName]; + + width = std::max( width, KIUI::GetTextSize( text, GetView() ).x ); + } + } + + return width; +} diff --git a/eeschema/fields_data_model.h b/eeschema/fields_data_model.h new file mode 100644 index 0000000000..9fc410c54a --- /dev/null +++ b/eeschema/fields_data_model.h @@ -0,0 +1,149 @@ +#include +#include +#include + +// The field name in the data model (translated) +#define DISPLAY_NAME_COLUMN 0 +// The field name's label for exporting (CSV, etc.) +#define LABEL_COLUMN 1 +#define SHOW_FIELD_COLUMN 2 +#define GROUP_BY_COLUMN 3 +// The internal field name (untranslated) +#define FIELD_NAME_COLUMN 4 + + +enum GROUP_TYPE +{ + GROUP_SINGLETON, + GROUP_COLLAPSED, + GROUP_COLLAPSED_DURING_SORT, + GROUP_EXPANDED, + CHILD_ITEM +}; + + +struct DATA_MODEL_ROW +{ + DATA_MODEL_ROW( const SCH_REFERENCE& aFirstReference, GROUP_TYPE aType ) + { + m_Refs.push_back( aFirstReference ); + m_Flag = aType; + } + + GROUP_TYPE m_Flag; + std::vector m_Refs; +}; + + +struct DATA_MODEL_COL +{ + wxString m_fieldName; + wxString m_label; + bool m_userAdded; + bool m_show; + bool m_group; +}; + + + +class FIELDS_EDITOR_GRID_DATA_MODEL : public wxGridTableBase +{ +public: + FIELDS_EDITOR_GRID_DATA_MODEL( SCH_EDIT_FRAME* aFrame, SCH_REFERENCE_LIST& aSymbolsList ) : + m_frame( aFrame ), + m_symbolsList( aSymbolsList ), + m_edited( false ), + m_sortColumn( 0 ), + m_sortAscending( false ) + { + m_symbolsList.SplitReferences(); + } + + void AddColumn( const wxString& aFieldName, const wxString& aLabel, bool aAddedByUser ); + void RemoveColumn( int aCol ); + void RenameColumn( int aCol, const wxString& newName ); + void MoveColumn( int aCol, int aNewPos ) { std::swap( m_cols[aCol], m_cols[aNewPos] ); } + + int GetNumberRows() override { return (int) m_rows.size(); } + int GetNumberCols() override { return (int) m_cols.size(); } + + void SetColLabelValue( int aCol, const wxString& aLabel ) override + { + m_cols[aCol].m_label = aLabel; + } + + + wxString GetColLabelValue( int aCol ) override { return m_cols[aCol].m_label; } + + wxString GetColFieldName( int aCol ) { return m_cols[aCol].m_fieldName; } + int GetFieldNameCol( wxString aFieldName ); + + const std::vector GetFieldsOrder(); + void SetFieldsOrder( const std::vector& aNewOrder ); + + bool IsEmptyCell( int aRow, int aCol ) override + { + return false; // don't allow adjacent cell overflow, even if we are actually empty + } + + wxString GetValue( int aRow, int aCol ) override; + wxString GetValue( const DATA_MODEL_ROW& group, int aCol ); + wxString GetRawValue( int aRow, int aCol ) { return GetValue( m_rows[aRow], aCol ); } + void SetValue( int aRow, int aCol, const wxString& aValue ) override; + + GROUP_TYPE GetRowFlags( int aRow ) { return m_rows[aRow].m_Flag; } + + std::vector GetRowReferences( int aRow ) const + { + wxCHECK( aRow < (int) m_rows.size(), std::vector() ); + return m_rows[aRow].m_Refs; + } + + bool ColIsReference( int aCol ) + { + return ( aCol < (int) m_cols.size() ) && m_cols[aCol].m_fieldName == _( "Reference" ); + } + + bool ColIsQuantity( int aCol ) + { + return ( aCol < (int) m_cols.size() ) && m_cols[aCol].m_fieldName == _( "Qty" ); + } + + void Sort( int aColumn, bool ascending ); + + void RebuildRows( wxSearchCtrl* aFilter, wxCheckBox* aGroupSymbolsBox, + wxDataViewListCtrl* aFieldsCtrl ); + void ExpandRow( int aRow ); + void CollapseRow( int aRow ); + void ExpandCollapseRow( int aRow ); + void CollapseForSort(); + void ExpandAfterSort(); + + void ApplyData(); + + bool IsEdited() { return m_edited; } + + int GetDataWidth( int aCol ); + +private: + static bool cmp( const DATA_MODEL_ROW& lhGroup, const DATA_MODEL_ROW& rhGroup, + FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol, bool ascending ); + bool unitMatch( const SCH_REFERENCE& lhRef, const SCH_REFERENCE& rhRef ); + bool groupMatch( const SCH_REFERENCE& lhRef, const SCH_REFERENCE& rhRef, + wxDataViewListCtrl* fieldsCtrl ); + +protected: + SCH_EDIT_FRAME* m_frame; + SCH_REFERENCE_LIST m_symbolsList; + bool m_edited; + int m_sortColumn; + bool m_sortAscending; + + std::vector m_cols; + std::vector m_rows; + + // Data store + // The data model is fundamentally m_componentRefs X m_fieldNames. + // A map of compID : fieldSet, where fieldSet is a map of fieldName : fieldValue + std::map> m_dataStore; +};