Symbol Fields Table: move data model into its own file
This commit is contained in:
parent
21c81b19fa
commit
cccd708860
|
@ -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
|
||||
|
|
|
@ -49,17 +49,9 @@
|
|||
#include <wx/filedlg.h>
|
||||
#include <dialogs/eda_view_switcher.h>
|
||||
#include "dialog_symbol_fields_table.h"
|
||||
#include <fields_data_model.h>
|
||||
#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<std::string, bool>( {
|
||||
|
@ -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<SCH_REFERENCE> 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<DATA_MODEL_COL> 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<wxString, wxString> > 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<wxString> GetFieldsOrder()
|
||||
{
|
||||
std::vector<wxString> fields;
|
||||
|
||||
for( auto col : m_cols )
|
||||
{
|
||||
fields.emplace_back( col.m_fieldName );
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
void SetFieldsOrder( const std::vector<wxString>& 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<SCH_REFERENCE> GetRowReferences( int aRow ) const
|
||||
{
|
||||
wxCHECK( aRow < (int)m_rows.size(), std::vector<SCH_REFERENCE>() );
|
||||
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<SCH_REFERENCE> 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. ~(a<b) != (a>b)
|
||||
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<WX_GRID*>( 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<DATA_MODEL_ROW> 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<wxString, wxString>& fieldStore = m_dataStore[symbol.m_Uuid];
|
||||
|
||||
for( const std::pair<wxString, wxString> 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;
|
||||
|
|
|
@ -81,6 +81,8 @@ private:
|
|||
void OnFieldsCtrlSelectionChanged( wxDataViewEvent& event ) override;
|
||||
bool TryBefore( wxEvent& aEvent ) override;
|
||||
|
||||
void OnPreviewRefresh( wxCommandEvent& event ) override;
|
||||
|
||||
std::vector<BOM_PRESET> GetUserBomPresets() const;
|
||||
void SetUserBomPresets( std::vector<BOM_PRESET>& aPresetList );
|
||||
void ApplyBomPreset( const wxString& aPresetName );
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -122,7 +122,7 @@
|
|||
<object class="notebookpage" expanded="1">
|
||||
<property name="bitmap"></property>
|
||||
<property name="label">Edit</property>
|
||||
<property name="select">1</property>
|
||||
<property name="select">0</property>
|
||||
<object class="wxPanel" expanded="1">
|
||||
<property name="BottomDockable">1</property>
|
||||
<property name="LeftDockable">1</property>
|
||||
|
@ -1224,7 +1224,7 @@
|
|||
<object class="notebookpage" expanded="1">
|
||||
<property name="bitmap"></property>
|
||||
<property name="label">Export</property>
|
||||
<property name="select">0</property>
|
||||
<property name="select">1</property>
|
||||
<object class="wxPanel" expanded="0">
|
||||
<property name="BottomDockable">1</property>
|
||||
<property name="LeftDockable">1</property>
|
||||
|
@ -2175,7 +2175,7 @@
|
|||
<property name="window_extra_style"></property>
|
||||
<property name="window_name"></property>
|
||||
<property name="window_style"></property>
|
||||
<event name="OnButtonClick">OnRegroupSymbols</event>
|
||||
<event name="OnButtonClick">OnPreviewRefresh</event>
|
||||
</object>
|
||||
</object>
|
||||
</object>
|
||||
|
|
|
@ -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(); }
|
||||
|
|
|
@ -0,0 +1,563 @@
|
|||
#include <wx/string.h>
|
||||
#include <wx/srchctrl.h>
|
||||
#include <wx/checkbox.h>
|
||||
#include <wx/debug.h>
|
||||
#include <wx/grid.h>
|
||||
#include <widgets/wx_grid.h>
|
||||
#include <sch_reference_list.h>
|
||||
#include <sch_edit_frame.h>
|
||||
#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<wxString> FIELDS_EDITOR_GRID_DATA_MODEL::GetFieldsOrder()
|
||||
{
|
||||
std::vector<wxString> fields;
|
||||
|
||||
for( auto col : m_cols )
|
||||
{
|
||||
fields.emplace_back( col.m_fieldName );
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
void FIELDS_EDITOR_GRID_DATA_MODEL::SetFieldsOrder( const std::vector<wxString>& 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<SCH_REFERENCE> 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. ~(a<b) != (a>b)
|
||||
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<WX_GRID*>( 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<DATA_MODEL_ROW> 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<wxString, wxString>& fieldStore = m_dataStore[symbol.m_Uuid];
|
||||
|
||||
for( const std::pair<wxString, wxString> 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;
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
#include <sch_reference_list.h>
|
||||
#include <wx/grid.h>
|
||||
#include <wx/srchctrl.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
|
||||
|
||||
|
||||
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<SCH_REFERENCE> 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<wxString> GetFieldsOrder();
|
||||
void SetFieldsOrder( const std::vector<wxString>& 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<SCH_REFERENCE> GetRowReferences( int aRow ) const
|
||||
{
|
||||
wxCHECK( aRow < (int) m_rows.size(), std::vector<SCH_REFERENCE>() );
|
||||
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<DATA_MODEL_COL> m_cols;
|
||||
std::vector<DATA_MODEL_ROW> 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<KIID, std::map<wxString, wxString>> m_dataStore;
|
||||
};
|
Loading…
Reference in New Issue