kicad-source/eeschema/fields_data_model.h
Wayne Stambaugh 6f352ccd96 Improve symbol fields table editor undo/redo memory usage.
- Do not add a modify action to commit for symbols that have no changes.
- Do not add a modify action to commit for each instance of shared symbols.
2025-09-05 11:46:05 -04:00

395 lines
13 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software: you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <sch_reference_list.h>
#include <wx/grid.h>
#include <widgets/wx_grid.h>
struct BOM_FIELD;
struct BOM_PRESET;
struct BOM_FMT_PRESET;
// Columns for the View Fields grid
#define DISPLAY_NAME_COLUMN 0 // The field name in the data model (translated)
#define LABEL_COLUMN 1 // The field name's label for exporting (CSV, etc.)
#define SHOW_FIELD_COLUMN 2
#define GROUP_BY_COLUMN 3
#define VIEW_FIELDS_COL_COUNT 4
// Data model for the list of fields to view (and to group-by) for the Symbol Fields Table
class VIEW_CONTROLS_GRID_DATA_MODEL : public WX_GRID_TABLE_BASE
{
public:
VIEW_CONTROLS_GRID_DATA_MODEL()
{}
~VIEW_CONTROLS_GRID_DATA_MODEL() override = default;
int GetNumberRows() override { return (int) m_fields.size(); }
int GetNumberCols() override { return VIEW_FIELDS_COL_COUNT; }
wxString GetColLabelValue( int aCol ) override;
bool IsEmptyCell( int aRow, int aCol ) override
{
return false; // don't allow adjacent cell overflow, even if we are actually empty
}
bool CanGetValueAs( int aRow, int aCol, const wxString& aTypeName ) override
{
switch( aCol )
{
case DISPLAY_NAME_COLUMN:
case LABEL_COLUMN: return aTypeName == wxGRID_VALUE_STRING;
case SHOW_FIELD_COLUMN:
case GROUP_BY_COLUMN: return aTypeName == wxGRID_VALUE_BOOL;
default: wxFAIL; return false;
}
}
bool CanSetValueAs( int aRow, int aCol, const wxString& aTypeName ) override
{
return CanGetValueAs( aRow, aCol, aTypeName );
}
wxString GetValue( int aRow, int aCol ) override;
bool GetValueAsBool( int aRow, int aCol ) override;
void SetValue( int aRow, int aCol, const wxString& aValue ) override;
void SetValueAsBool( int aRow, int aCol, bool aValue ) override;
void AppendRow( const wxString& aFieldName, const wxString& aBOMName, bool aShow, bool aGroupBy );
void DeleteRow( int aRow );
wxString GetCanonicalFieldName( int aRow );
void SetCanonicalFieldName( int aRow, const wxString& aName );
protected:
std::vector<BOM_FIELD> m_fields;
};
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_ItemNumber = 0;
m_Refs.push_back( aFirstReference );
m_Flag = aType;
}
int m_ItemNumber;
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 WX_GRID_TABLE_BASE
{
public:
enum SCOPE : int
{
SCOPE_ALL = 0,
SCOPE_SHEET = 1,
SCOPE_SHEET_RECURSIVE = 2
};
FIELDS_EDITOR_GRID_DATA_MODEL( const SCH_REFERENCE_LIST& aSymbolsList, wxGridCellAttr* aURLEditor ) :
m_symbolsList( aSymbolsList ),
m_edited( false ),
m_sortColumn( 0 ),
m_sortAscending( false ),
m_scope( SCOPE_ALL ),
m_groupingEnabled( false ),
m_excludeDNP( false ),
m_includeExcluded( false ),
m_rebuildsEnabled( true ),
m_urlEditor( aURLEditor )
{
m_symbolsList.SplitReferences();
}
~FIELDS_EDITOR_GRID_DATA_MODEL() override
{
wxSafeDecRef( m_urlEditor );
}
static const wxString QUANTITY_VARIABLE;
static const wxString ITEM_NUMBER_VARIABLE;
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 )
{
wxCHECK_RET( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), "Invalid Column Number" );
if( aCol == aNewPos )
{
return;
}
else if( aCol < aNewPos )
{
std::rotate( std::begin( m_cols ) + aCol, std::begin( m_cols ) + aCol + 1,
std::begin( m_cols ) + aNewPos + 1 );
}
else
{
std::rotate( std::begin( m_cols ) + aNewPos, std::begin( m_cols ) + aCol,
std::begin( m_cols ) + aCol + 1 );
}
}
int GetNumberRows() override { return (int) m_rows.size(); }
int GetNumberCols() override { return (int) m_cols.size(); }
void SetColLabelValue( int aCol, const wxString& aLabel ) override
{
wxCHECK_RET( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), "Invalid Column Number" );
m_cols[aCol].m_label = aLabel;
}
wxString GetColLabelValue( int aCol ) override
{
wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), wxString() );
return m_cols[aCol].m_label;
}
wxString GetColFieldName( int aCol )
{
wxCHECK( aCol >= 0 && aCol < static_cast<int>( m_cols.size() ), wxString() );
return m_cols[aCol].m_fieldName;
}
int GetFieldNameCol( const wxString& aFieldName ) const;
std::vector<BOM_FIELD> GetFieldsOrdered();
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;
wxGridCellAttr* GetAttr( int aRow, int aCol, wxGridCellAttr::wxAttrKind aKind ) override;
wxString GetValue( const DATA_MODEL_ROW& group, int aCol,
const wxString& refDelimiter = wxT( ", " ),
const wxString& refRangDelimiter = wxT( "-" ),
bool resolveVars = false,
bool listMixedValues = false );
wxString GetExportValue( int aRow, int aCol, const wxString& refDelimiter,
const wxString& refRangeDelimiter )
{
return GetValue( m_rows[aRow], aCol, refDelimiter, refRangeDelimiter, true, true );
}
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 >= 0 && aRow < (int) m_rows.size(), std::vector<SCH_REFERENCE>() );
return m_rows[aRow].m_Refs;
}
bool ColIsReference( int aCol );
bool ColIsValue( int aCol );
bool ColIsQuantity( int aCol );
bool ColIsItemNumber( int aCol );
bool ColIsAttribute( int aCol );
void SetSorting( int aCol, bool ascending )
{
wxCHECK_RET( aCol >= 0 && aCol < (int) m_cols.size(), "Invalid Column Number" );
m_sortColumn = aCol;
m_sortAscending = ascending;
}
int GetSortCol() { return m_sortColumn; }
bool GetSortAsc() { return m_sortAscending; }
// These are used to disable the RebuildRows functionality while we're generating
// lots of events in the UI, e.g. applying a BOM preset, that would thrash the grid.
void EnableRebuilds();
void DisableRebuilds();
void RebuildRows();
void ExpandRow( int aRow );
void CollapseRow( int aRow );
void ExpandCollapseRow( int aRow );
void CollapseForSort();
void ExpandAfterSort();
void ApplyData( SCH_COMMIT& aCommit, TEMPLATES& aTemplateFieldnames );
bool IsEdited() { return m_edited; }
int GetDataWidth( int aCol );
void SetFilter( const wxString& aFilter ) { m_filter = aFilter; }
const wxString& GetFilter() { return m_filter; }
void SetScope( SCOPE aScope ) { m_scope = aScope; }
SCOPE GetScope() { return m_scope; }
void SetPath( const SCH_SHEET_PATH& aPath ) { m_path = aPath; }
const SCH_SHEET_PATH& GetPath() { return m_path; }
void SetGroupingEnabled( bool group ) { m_groupingEnabled = group; }
bool GetGroupingEnabled() { return m_groupingEnabled; }
/* These contradictorily named functions force including symbols that
* have the Exclude from BOM check box ticked. This is needed so we can view
* these parts in the symbol fields table dialog, while also excluding from the
* BOM export */
void SetIncludeExcludedFromBOM( bool include ) { m_includeExcluded = include; }
bool GetIncludeExcludedFromBOM() { return m_includeExcluded; }
void SetExcludeDNP( bool exclude ) { m_excludeDNP = exclude; }
bool GetExcludeDNP() { return m_excludeDNP; }
void SetGroupColumn( int aCol, bool group )
{
wxCHECK_RET( aCol >= 0 && aCol < (int) m_cols.size(), "Invalid Column Number" );
m_cols[aCol].m_group = group;
}
bool GetGroupColumn( int aCol )
{
wxCHECK_MSG( aCol >= 0 && aCol < (int) m_cols.size(), false, "Invalid Column Number" );
return m_cols[aCol].m_group;
}
void SetShowColumn( int aCol, bool show )
{
wxCHECK_RET( aCol >= 0 && aCol < (int) m_cols.size(), "Invalid Column Number" );
m_cols[aCol].m_show = show;
}
bool GetShowColumn( int aCol )
{
wxCHECK_MSG( aCol >= 0 && aCol < (int) m_cols.size(), false, "Invalid Column Number" );
return m_cols[aCol].m_show;
}
void ApplyBomPreset( const BOM_PRESET& preset );
BOM_PRESET GetBomSettings();
wxString Export( const BOM_FMT_PRESET& settings );
void AddReferences( const SCH_REFERENCE_LIST& aRefs );
void RemoveReferences( const SCH_REFERENCE_LIST& aRefs );
void RemoveSymbol( const SCH_SYMBOL& aSymbol );
void UpdateReferences( const SCH_REFERENCE_LIST& aRefs );
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 );
// Helper functions to deal with translating wxGrid values to and from
// named field values like ${DNP}
bool isAttribute( const wxString& aFieldName );
wxString getAttributeValue( const SCH_SYMBOL&, const wxString& aAttributeName );
/**
* Set the attribute value.
*
* @param aSymbol is the symbol to set the attribute.
* @param aAttributeName is the name of the symbol attribute.
* @param aValue is the value to set the attribute.
* @retval true if the symbol attribute value has changed.
* @retval false if the symbol attribute has **not** changed.
*/
bool setAttributeValue( SCH_SYMBOL& aSymbol, const wxString& aAttributeName,
const wxString& aValue );
/* Helper function to get the resolved field value.
* Handles symbols that are missing fields that would have a variable
* in their value because their name is the same as a variable.
* Example: BOM template provides ${DNP} as a field, but they symbol doesn't have the field. */
wxString getFieldShownText( const SCH_REFERENCE& aRef, const wxString& aFieldName );
void Sort();
SCH_REFERENCE_LIST getSymbolReferences( SCH_SYMBOL* aSymbol );
void storeReferenceFields( SCH_REFERENCE& aRef );
void updateDataStoreSymbolField( const SCH_SYMBOL& aSymbol, const wxString& aFieldName );
protected:
/**
* The flattened by hierarchy list of symbols.
*
* @warning This list **must** be kept sorted by symbol pointer. Otherwise, the undo/redo
* commit actions will be broken.
*/
SCH_REFERENCE_LIST m_symbolsList;
bool m_edited;
int m_sortColumn;
bool m_sortAscending;
wxString m_filter;
enum SCOPE m_scope;
SCH_SHEET_PATH m_path;
bool m_groupingEnabled;
bool m_excludeDNP;
bool m_includeExcluded;
bool m_rebuildsEnabled;
wxGridCellAttr* m_urlEditor;
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;
};