/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2024 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 . */ #include #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 struct BOM_FIELD; struct BOM_PRESET; struct BOM_FMT_PRESET; enum GROUP_TYPE { GROUP_SINGLETON, GROUP_COLLAPSED, GROUP_COLLAPSED_DURING_SORT, GROUP_EXPANDED, CHILD_ITEM }; struct LIB_DATA_MODEL_ROW { LIB_DATA_MODEL_ROW( const LIB_SYMBOL* aFirstReference, GROUP_TYPE aType ) : m_ItemNumber( 0 ), m_Flag( aType ), m_Refs( { aFirstReference } ) { } int m_ItemNumber; GROUP_TYPE m_Flag; std::vector m_Refs; }; struct LIB_DATA_MODEL_COL { wxString m_fieldName; wxString m_label; bool m_userAdded; bool m_show; bool m_group; bool m_isCheckbox; }; struct LIB_DATA_ELEMENT { LIB_DATA_ELEMENT() { m_originalData = wxEmptyString; m_currentData = wxEmptyString; m_originallyEmpty = false; m_currentlyEmpty = false; m_isModified = false; m_isStriped = false; m_createDerivedSymbol = false; m_derivedSymbolName = wxEmptyString; } wxString m_originalData; wxString m_currentData; bool m_originallyEmpty; bool m_currentlyEmpty; bool m_isModified; bool m_isStriped; bool m_createDerivedSymbol; wxString m_derivedSymbolName; }; class LIB_FIELDS_EDITOR_GRID_DATA_MODEL : public wxGridTableBase { public: enum SCOPE : int { SCOPE_ALL = 0, SCOPE_SHEET = 1, SCOPE_SHEET_RECURSIVE = 2 }; LIB_FIELDS_EDITOR_GRID_DATA_MODEL( const std::vector& aSymbolsList ) : m_symbolsList( aSymbolsList ), m_edited( false ), m_sortColumn( 0 ), m_sortAscending( false ), m_filter( wxEmptyString ), m_scope( SCOPE_ALL ), m_path(), m_groupingEnabled( false ), m_cols(), m_rows(), m_dataStore(), m_stripedStringRenderer( nullptr ) { } ~LIB_FIELDS_EDITOR_GRID_DATA_MODEL() { for (auto& pair : m_stripedRenderers) pair.second->DecRef(); m_stripedRenderers.clear(); for( const auto& [col, attr] : m_colAttrs ) wxSafeDecRef( attr ); } static const wxString QUANTITY_VARIABLE; static const wxString ITEM_NUMBER_VARIABLE; void CreateDerivedSymbol( int aRow, int aCol, wxString& aNewSymbolName ); void CreateDerivedSymbolImmediate( int aRow, int aCol, wxString& aNewSymbolName ); void AddColumn( const wxString& aFieldName, const wxString& aLabel, bool aAddedByUser, bool aIsCheckbox ); void RemoveColumn( int aCol ); void RenameColumn( int aCol, const wxString& newName ); void MoveColumn( int aCol, int aNewPos ) { wxCHECK_RET( aCol >= 0 && aCol < (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 < (int) m_cols.size(), "Invalid Column Number" ); m_cols[aCol].m_label = aLabel; } wxString GetColLabelValue( int aCol ) override { wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), wxString() ); return m_cols[aCol].m_label; } wxString GetColFieldName( int aCol ) { wxCHECK( aCol >= 0 && aCol < (int) m_cols.size(), wxString() ); return m_cols[aCol].m_fieldName; } int GetFieldNameCol( const wxString& aFieldName ); 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 GetTypeName( int row, int col ) override; wxString GetValue( const LIB_DATA_MODEL_ROW& group, int aCol ); void SetValue( int aRow, int aCol, const wxString& aValue ) override; wxGridCellAttr* GetAttr( int row, int col, wxGridCellAttr::wxAttrKind kind ) override; void RevertRow( int aRow ); void ClearCell( int aRow, int aCol ); bool ColIsValue( int aCol ); bool ColIsCheck( 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; } const LIB_SYMBOL* GetSymbolForRow( int aRow ) { wxCHECK( aRow >= 0 && aRow < (int) m_rows.size(), nullptr ); return m_rows[aRow].m_Refs[0]; } void GetSymbolNames( wxArrayString& aList ) { aList.Clear(); for( const LIB_SYMBOL* symbol : m_symbolsList ) aList.Add( symbol->GetName() ); } void RebuildRows(); void ExpandRow( int aRow ); void CollapseRow( int aRow ); void ExpandCollapseRow( int aRow ); void CollapseForSort(); void ExpandAfterSort(); void ApplyData( std::function symbolChangeHandler, std::function postApplyHandler = nullptr ); /// Get and clear the list of newly created derived symbols for library manager processing std::vector> GetAndClearCreatedDerivedSymbols() { auto result = std::move( m_createdDerivedSymbols ); m_createdDerivedSymbols.clear(); return result; } bool IsEdited() { return m_edited; } int GetDataWidth( int aCol ); void SetFilter( const wxString& aFilter ) { m_filter = aFilter; } const wxString& GetFilter() { return m_filter; } void SetGroupingEnabled( bool group ) { m_groupingEnabled = group; } bool GetGroupingEnabled() { return m_groupingEnabled; } 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; } bool IsRowEditable( int aRow ) { wxCHECK_MSG( aRow >= 0 && aRow < (int) m_rows.size(), false, "Invalid Row Number" ); return m_rows[aRow].m_Flag == GROUP_SINGLETON || m_rows[aRow].m_Flag == GROUP_SINGLETON; } bool IsCellEdited( int aRow, int aCol ) { wxCHECK_MSG( aRow >= 0 && aRow < (int) m_rows.size(), false, "Invalid Row Number" ); wxCHECK_MSG( aCol >= 0 && aCol < (int) m_cols.size(), false, "Invalid Column Number" ); return m_dataStore[m_rows[aRow].m_Refs[0]->m_Uuid][m_cols[aCol].m_fieldName].m_isModified; } bool IsCellClear( int aRow, int aCol ) { wxCHECK_MSG( aRow >= 0 && aRow < (int) m_rows.size(), false, "Invalid Row Number" ); wxCHECK_MSG( aCol >= 0 && aCol < (int) m_cols.size(), false, "Invalid Column Number" ); return m_dataStore[m_rows[aRow].m_Refs[0]->m_Uuid][m_cols[aCol].m_fieldName].m_currentlyEmpty; } bool IsRowSingleSymbol( int aRow ) { wxCHECK_MSG( aRow >= 0 && aRow < (int) m_rows.size(), false, "Invalid Row Number" ); return m_rows[aRow].m_Flag == GROUP_SINGLETON || m_rows[aRow].m_Flag == CHILD_ITEM; } void SetColAttr( wxGridCellAttr* aAttr, int aCol ) override { wxSafeDecRef( m_colAttrs[aCol] ); m_colAttrs[aCol] = aAttr; } private: static bool cmp( const LIB_DATA_MODEL_ROW& lhGroup, const LIB_DATA_MODEL_ROW& rhGroup, LIB_FIELDS_EDITOR_GRID_DATA_MODEL* dataModel, int sortCol, bool ascending ); bool groupMatch( const LIB_SYMBOL* lhRef, const LIB_SYMBOL* rhRef ); wxString getAttributeValue( const LIB_SYMBOL*, const wxString& aAttributeName ); void setAttributeValue( LIB_SYMBOL* aSymbol, const wxString& aAttributeName, const wxString& aValue ); void createActualDerivedSymbol( const LIB_SYMBOL* aParentSymbol, const wxString& aNewSymbolName, const KIID& aNewSymbolUuid ); void Sort(); void updateDataStoreSymbolField( const LIB_SYMBOL* aSymbol, const wxString& aFieldName ); bool isStripeableField( int aCol ); wxGridCellRenderer* getStripedRenderer( int aCol ) const; protected: std::vector 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; 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 : LIB_DATA_ELEMENT std::map> m_dataStore; // Track newly created derived symbols for library manager integration std::vector> m_createdDerivedSymbols; // symbol, library name // stripe bitmap support mutable STRIPED_STRING_RENDERER* m_stripedStringRenderer; mutable std::map m_stripedRenderers; // Column attributes storage std::map m_colAttrs; };