kicad/eeschema/fields_data_model.cpp

564 lines
18 KiB
C++
Raw Normal View History

#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;
}