/* * 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 . */ #ifndef PCB_NET_INSPECTOR_PANEL_DATA_MODEL #define PCB_NET_INSPECTOR_PANEL_DATA_MODEL /** * Primary data item for entries in the Net Inspector list. * * This class tracks all data for a given net entry in the net inspector list. */ class PCB_NET_INSPECTOR_PANEL::LIST_ITEM { public: enum class GROUP_TYPE { NONE, USER_DEFINED, NETCLASS }; LIST_ITEM( unsigned int aGroupNumber, const wxString& aGroupName, GROUP_TYPE aGroupType ) : m_group_type( aGroupType ), m_group_number( aGroupNumber ), m_net_name( aGroupName ) { m_group_name = aGroupName; m_column_changed.resize( COLUMN_LAST_STATIC_COL + 1 + MAX_CU_LAYERS, 0 ); } LIST_ITEM( NETINFO_ITEM* aNet ) : m_group_type( GROUP_TYPE::NONE ), m_net( aNet ) { wxASSERT( aNet ); m_net_name = UnescapeString( aNet->GetNetname() ); m_net_class = UnescapeString( aNet->GetNetClass()->GetName() ); m_column_changed.resize( COLUMN_LAST_STATIC_COL + 1 + MAX_CU_LAYERS, 0 ); } LIST_ITEM() { m_column_changed.resize( COLUMN_LAST_STATIC_COL + 1 + MAX_CU_LAYERS, 0 ); } LIST_ITEM& operator=( const LIST_ITEM& ) = delete; bool GetIsGroup() const { return m_group_type != GROUP_TYPE::NONE; } const wxString& GetGroupName() const { return m_group_name; } GROUP_TYPE GetGroupType() const { return m_group_type; } int GetGroupNumber() const { return m_group_number; } auto ChildrenBegin() const { return m_children.begin(); } auto ChildrenEnd() const { return m_children.end(); } unsigned int ChildrenCount() const { return m_children.size(); } NETINFO_ITEM* GetNet() const { return m_net; } int GetNetCode() const { return GetIsGroup() ? ( 0 - int( m_group_number ) - 1 ) : m_net->GetNetCode(); } const wxString& GetNetName() const { return m_net_name; } const wxString& GetNetclassName() const { return m_net_class; } void ResetColumnChangedBits() { std::fill( m_column_changed.begin(), m_column_changed.end(), 0 ); } unsigned int GetPadCount() const { return m_pad_count; } bool PadCountChanged() const { return m_column_changed[COLUMN_PAD_COUNT]; } void SetPadCount( unsigned int aValue ) { if( m_parent ) m_parent->SetPadCount( m_parent->GetPadCount() - m_pad_count + aValue ); m_column_changed[COLUMN_PAD_COUNT] |= ( m_pad_count != aValue ); m_pad_count = aValue; } void AddPadCount( unsigned int aValue ) { if( m_parent ) m_parent->AddPadCount( aValue ); m_column_changed[COLUMN_PAD_COUNT] |= ( aValue != 0 ); m_pad_count += aValue; } void SubPadCount( unsigned int aValue ) { if( m_parent ) m_parent->SubPadCount( aValue ); m_column_changed[COLUMN_PAD_COUNT] |= ( aValue != 0 ); m_pad_count -= aValue; } unsigned GetViaCount() const { return m_via_count; } bool ViaCountChanged() const { return m_column_changed[COLUMN_VIA_COUNT]; } void SetViaCount( unsigned int aValue ) { if( m_parent ) m_parent->SetViaCount( m_parent->GetViaCount() - m_via_count + aValue ); m_column_changed[COLUMN_VIA_COUNT] |= ( m_via_count != aValue ); m_via_count = aValue; } void AddViaCount( unsigned int aValue ) { if( m_parent ) m_parent->AddViaCount( aValue ); m_column_changed[COLUMN_VIA_COUNT] |= ( aValue != 0 ); m_via_count += aValue; } void SubViaCount( unsigned int aValue ) { if( m_parent ) m_parent->SubViaCount( aValue ); m_column_changed[COLUMN_VIA_COUNT] |= ( aValue != 0 ); m_via_count -= aValue; } uint64_t GetViaLength() const { return m_via_length; } bool ViaLengthChanged() const { return m_column_changed[COLUMN_VIA_LENGTH]; } void SetViaLength( unsigned int aValue ) { if( m_parent ) m_parent->SetViaLength( m_parent->GetViaLength() - m_via_length + aValue ); m_column_changed[COLUMN_VIA_LENGTH] |= ( m_via_length != aValue ); m_via_length = aValue; } void AddViaLength( unsigned int aValue ) { if( m_parent ) m_parent->AddViaLength( aValue ); m_column_changed[COLUMN_VIA_LENGTH] |= ( aValue != 0 ); m_via_length += aValue; } void SubViaLength( uint64_t aValue ) { if( m_parent ) m_parent->SubViaLength( aValue ); m_column_changed[COLUMN_VIA_LENGTH] |= ( aValue != 0 ); m_via_length -= aValue; } uint64_t GetBoardWireLength() const { uint64_t retval = 0; for( uint64_t val : m_layer_wire_length ) retval += val; return retval; } uint64_t GetLayerWireLength( size_t aLayer ) const { wxCHECK_MSG( aLayer < m_layer_wire_length.size(), 0, wxT( "Invalid layer specified" ) ); return m_layer_wire_length[aLayer]; } bool BoardWireLengthChanged() const { return m_column_changed[COLUMN_BOARD_LENGTH]; } void SetLayerWireLength( const uint64_t aValue, size_t aLayer ) { wxCHECK_RET( aLayer < m_layer_wire_length.size(), wxT( "Invalid layer specified" ) ); if( m_parent ) m_parent->SetLayerWireLength( m_parent->GetBoardWireLength() - m_layer_wire_length[aLayer] + aValue, aLayer ); m_column_changed[COLUMN_BOARD_LENGTH] |= ( m_layer_wire_length[aLayer] != aValue ); m_layer_wire_length[aLayer] = aValue; } void AddLayerWireLength( const uint64_t aValue, size_t aLayer ) { if( m_parent ) m_parent->AddLayerWireLength( aValue, aLayer ); m_column_changed[COLUMN_BOARD_LENGTH] |= ( m_layer_wire_length[aLayer] != 0 ); m_layer_wire_length[aLayer] += aValue; } void SubLayerWireLength( const uint64_t aValue, size_t aLayer ) { if( m_parent ) m_parent->SubLayerWireLength( aValue, aLayer ); m_column_changed[COLUMN_BOARD_LENGTH] |= ( m_layer_wire_length[aLayer] != 0 ); m_layer_wire_length[aLayer] -= aValue; } uint64_t GetPadDieLength() const { return m_pad_die_length; } bool PadDieLengthChanged() const { return m_column_changed[COLUMN_PAD_DIE_LENGTH]; } void SetPadDieLength( uint64_t aValue ) { if( m_parent ) m_parent->SetPadDieLength( m_parent->GetPadDieLength() - m_pad_die_length + aValue ); m_column_changed[COLUMN_PAD_DIE_LENGTH] |= ( m_pad_die_length != aValue ); m_pad_die_length = aValue; } void AddPadDieLength( uint64_t aValue ) { if( m_parent ) m_parent->AddPadDieLength( aValue ); m_column_changed[COLUMN_PAD_DIE_LENGTH] |= ( aValue != 0 ); m_pad_die_length += aValue; } void SubPadDieLength( uint64_t aValue ) { if( m_parent ) m_parent->SubPadDieLength( aValue ); m_column_changed[COLUMN_PAD_DIE_LENGTH] |= ( aValue != 0 ); m_pad_die_length -= aValue; } // the total length column is always computed, never stored. unsigned long long int GetTotalLength() const { return GetBoardWireLength() + GetViaLength() + GetPadDieLength(); } bool TotalLengthChanged() const { return BoardWireLengthChanged() || ViaLengthChanged() || PadDieLengthChanged(); } LIST_ITEM* Parent() const { return m_parent; } void SetParent( LIST_ITEM* aParent ) { if( m_parent == aParent ) return; if( m_parent != nullptr ) { m_parent->SubPadCount( GetPadCount() ); m_parent->SubViaCount( GetViaCount() ); m_parent->SubViaLength( GetViaLength() ); for( size_t ii = 0; ii < m_layer_wire_length.size(); ++ii ) m_parent->SubLayerWireLength( m_layer_wire_length[ii], ii ); m_parent->SubPadDieLength( GetPadDieLength() ); m_parent->m_children.erase( std::find( m_parent->m_children.begin(), m_parent->m_children.end(), this ) ); } m_parent = aParent; if( m_parent != nullptr ) { m_parent->AddPadCount( GetPadCount() ); m_parent->AddViaCount( GetViaCount() ); m_parent->AddViaLength( GetViaLength() ); for( size_t ii = 0; ii < m_layer_wire_length.size(); ++ii ) m_parent->AddLayerWireLength( m_layer_wire_length[ii], ii ); m_parent->AddPadDieLength( GetPadDieLength() ); m_parent->m_children.push_back( this ); } } private: LIST_ITEM* m_parent = nullptr; std::vector m_children; GROUP_TYPE m_group_type = GROUP_TYPE::NONE; unsigned int m_group_number = 0; NETINFO_ITEM* m_net = nullptr; unsigned int m_pad_count = 0; unsigned int m_via_count = 0; uint64_t m_via_length = 0; uint64_t m_pad_die_length = 0; std::array m_layer_wire_length{}; // Dirty bits to record when some attribute has changed, in order to avoid unnecessary sort // operations. // The values are semantically bools, but STL auto-promotes a std::vector to a bitset, // and then operator|= doesn't work. std::vector m_column_changed; // cached formatted names for faster display sorting wxString m_net_name; wxString m_net_class; wxString m_group_name; }; struct PCB_NET_INSPECTOR_PANEL::LIST_ITEM_NETCODE_CMP_LESS { template bool operator()( const T& a, const T& b ) const { return a->GetNetCode() < b->GetNetCode(); } template bool operator()( const T& a, int b ) const { return a->GetNetCode() < b; } template bool operator()( int a, const T& b ) const { return a < b->GetNetCode(); } }; struct PCB_NET_INSPECTOR_PANEL::LIST_ITEM_GROUP_NUMBER_CMP_LESS { template bool operator()( const T& a, const T& b ) const { return a->GetGroupNumber() < b->GetGroupNumber(); } template bool operator()( const T& a, int b ) const { return a->GetGroupNumber() < b; } template bool operator()( int a, const T& b ) const { return a < b->GetGroupNumber(); } }; /** * Data model for display in the Net Inspector panel. */ class PCB_NET_INSPECTOR_PANEL::DATA_MODEL : public wxDataViewModel { public: DATA_MODEL( PCB_NET_INSPECTOR_PANEL& parent ) : m_parent( parent ) {} unsigned int columnCount() const { return m_parent.m_columns.size(); } unsigned int itemCount() const { return m_items.size(); } wxVariant valueAt( unsigned int aCol, unsigned int aRow ) const { wxVariant r; GetValue( r, wxDataViewItem( const_cast( &*( m_items[aRow] ) ) ), aCol ); return r; } const LIST_ITEM& itemAt( unsigned int aRow ) const { return *m_items.at( aRow ); } std::vector> getGroupDataViewItems() { std::vector> ret; for( std::unique_ptr& item : m_items ) { if( item->GetIsGroup() ) ret.push_back( std::make_pair( item->GetGroupName(), wxDataViewItem( item.get() ) ) ); } return ret; } std::optional findItem( int aNetCode ) { auto i = std::lower_bound( m_items.begin(), m_items.end(), aNetCode, LIST_ITEM_NETCODE_CMP_LESS() ); if( i == m_items.end() || ( *i )->GetNetCode() != aNetCode ) return std::nullopt; return { i }; } std::optional findItem( NETINFO_ITEM* aNet ) { if( aNet != nullptr ) return findItem( aNet->GetNetCode() ); else return std::nullopt; } std::optional findGroupItem( int aGroupNumber ) { auto i = std::lower_bound( m_items.begin(), m_items.end(), aGroupNumber, LIST_ITEM_GROUP_NUMBER_CMP_LESS() ); if( i == m_items.end() || ( *i )->GetGroupNumber() != aGroupNumber ) return std::nullopt; return { i }; } LIST_ITEM_ITER addGroup( LIST_ITEM_ITER groupsBegin, LIST_ITEM_ITER groupsEnd, wxString groupName, LIST_ITEM::GROUP_TYPE groupType ) { LIST_ITEM_ITER group = std::find_if( groupsBegin, groupsEnd, [&]( const std::unique_ptr& x ) { return x->GetGroupName() == groupName && x->GetGroupType() == groupType; } ); if( group == groupsEnd ) { int dist = std::distance( groupsBegin, groupsEnd ); std::unique_ptr groupItem = std::make_unique( dist, groupName, groupType ); group = m_items.insert( groupsEnd, std::move( groupItem ) ); ItemAdded( wxDataViewItem( ( *group )->Parent() ), wxDataViewItem( &**group ) ); } return group; } std::optional addItem( std::unique_ptr aItem ) { if( aItem == nullptr ) return {}; bool groupMatched = false; // First see if item matches a group-by rule if( m_parent.m_custom_group_rules.size() > 0 ) { wxString searchName = aItem->GetNetName().Upper(); for( const wxString& groupName : m_parent.m_custom_group_rules ) { if( searchName.Find( groupName.Upper() ) != wxNOT_FOUND ) { aItem->SetParent( m_custom_group_map[groupName] ); groupMatched = true; break; } } } // Then add any netclass groups required by this item if( m_parent.m_group_by_netclass && !groupMatched ) { LIST_ITEM_ITER groups_begin = m_items.begin(); LIST_ITEM_ITER groups_end = std::find_if_not( m_items.begin(), m_items.end(), []( const std::unique_ptr& x ) { return x->GetIsGroup(); } ); wxString match_str = aItem->GetNetclassName(); LIST_ITEM_ITER group = addGroup( groups_begin, groups_end, match_str, LIST_ITEM::GROUP_TYPE::NETCLASS ); aItem->SetParent( &**group ); } // Now add the item itself. Usually when new nets are added, // they always get a higher netcode number than the already existing ones. // however, if we've got filtering enabled, we might not have all the nets in // our list, so do a sorted insertion. auto new_iter = std::lower_bound( m_items.begin(), m_items.end(), aItem->GetNetCode(), LIST_ITEM_NETCODE_CMP_LESS() ); new_iter = m_items.insert( new_iter, std::move( aItem ) ); const std::unique_ptr& new_item = *new_iter; ItemAdded( wxDataViewItem( new_item->Parent() ), wxDataViewItem( new_item.get() ) ); return { new_iter }; } void addItems( std::vector> aItems ) { m_items.reserve( m_items.size() + aItems.size() ); for( std::unique_ptr& i : aItems ) addItem( std::move( i ) ); } std::unique_ptr deleteItem( const std::optional& aRow ) { if( !aRow ) return {}; std::unique_ptr i = std::move( **aRow ); LIST_ITEM* parent = i->Parent(); i->SetParent( nullptr ); m_items.erase( *aRow ); ItemDeleted( wxDataViewItem( parent ), wxDataViewItem( &*i ) ); if( parent ) { ItemChanged( wxDataViewItem( parent ) ); if( m_parent.m_group_by_netclass && parent != nullptr && parent->ChildrenCount() == 0 ) { auto p = std::find_if( m_items.begin(), m_items.end(), [&]( std::unique_ptr& x ) { return x.get() == parent; } ); wxASSERT( p != m_items.end() ); m_items.erase( p ); ItemDeleted( wxDataViewItem( parent->Parent() ), wxDataViewItem( parent ) ); } } Resort(); return i; } /** * Adds all custom group-by entries to the items table * * Note this assumes that m_items is empty prior to adding these groups */ void addCustomGroups() { m_custom_group_map.clear(); int groupId = 0; for( const wxString& groupName : m_parent.m_custom_group_rules ) { std::unique_ptr& group = m_items.emplace_back( std::make_unique( groupId, groupName, LIST_ITEM::GROUP_TYPE::USER_DEFINED ) ); m_custom_group_map[groupName] = group.get(); ItemAdded( wxDataViewItem( group->Parent() ), wxDataViewItem( group.get() ) ); ++groupId; } } void deleteAllItems() { BeforeReset(); m_items.clear(); AfterReset(); } void updateItem( const std::optional& aRow ) { if( aRow ) { const std::unique_ptr& listItem = *aRow.value(); if( listItem->Parent() ) ItemChanged( wxDataViewItem( listItem->Parent() ) ); ItemChanged( wxDataViewItem( listItem.get() ) ); resortIfChanged( listItem.get() ); } } void updateAllItems() { for( std::unique_ptr& i : m_items ) ItemChanged( wxDataViewItem( i.get() ) ); } void resortIfChanged( LIST_ITEM* aItem ) { if( wxDataViewColumn* column = m_parent.m_netsList->GetSortingColumn() ) { bool changed = false; for( const LIST_ITEM* i = aItem; i != nullptr; i = i->Parent() ) changed |= itemColumnChanged( i, column->GetModelColumn() ); for( LIST_ITEM* i = aItem; i != nullptr; i = i->Parent() ) i->ResetColumnChangedBits(); if( changed ) Resort(); } } bool itemColumnChanged( const LIST_ITEM* aItem, unsigned int aCol ) const { if( aItem == nullptr || aCol >= m_parent.m_columns.size() ) return false; if( aCol == COLUMN_PAD_COUNT ) return aItem->PadCountChanged(); else if( aCol == COLUMN_VIA_COUNT ) return aItem->ViaCountChanged(); else if( aCol == COLUMN_VIA_LENGTH ) return aItem->ViaLengthChanged(); else if( aCol == COLUMN_BOARD_LENGTH ) return aItem->BoardWireLengthChanged(); else if( aCol == COLUMN_PAD_DIE_LENGTH ) return aItem->PadDieLengthChanged(); else if( aCol == COLUMN_TOTAL_LENGTH ) return aItem->TotalLengthChanged(); else if( aCol > COLUMN_LAST_STATIC_COL ) return aItem->BoardWireLengthChanged(); return false; } // implementation of wxDataViewModel interface // these are used to query the data model by the GUI view implementation. // these are not supposed to be used to modify the data model. for that // use the public functions above. protected: unsigned int GetColumnCount() const override { return columnCount(); } void GetValue( wxVariant& aOutValue, const wxDataViewItem& aItem, unsigned int aCol ) const override { if( LIST_ITEM* i = static_cast( aItem.GetID() ) ) { if( i->GetIsGroup() ) { if( aCol == COLUMN_NAME ) switch( i->GetGroupType() ) { case LIST_ITEM::GROUP_TYPE::NETCLASS: aOutValue = _( "Netclass" ) + ": " + i->GetGroupName(); break; case LIST_ITEM::GROUP_TYPE::USER_DEFINED: aOutValue = _( "Custom" ) + ": " + i->GetGroupName(); break; default: aOutValue = i->GetGroupName(); break; } else aOutValue = ""; } else if( aCol == COLUMN_NAME ) aOutValue = i->GetNetName(); else if( aCol == COLUMN_NETCLASS ) aOutValue = i->GetNetclassName(); else if( aCol == COLUMN_PAD_COUNT ) aOutValue = m_parent.formatCount( i->GetPadCount() ); else if( aCol == COLUMN_VIA_COUNT ) aOutValue = m_parent.formatCount( i->GetViaCount() ); else if( aCol == COLUMN_VIA_LENGTH ) aOutValue = m_parent.formatLength( i->GetViaLength() ); else if( aCol == COLUMN_BOARD_LENGTH ) aOutValue = m_parent.formatLength( i->GetBoardWireLength() ); else if( aCol == COLUMN_PAD_DIE_LENGTH ) aOutValue = m_parent.formatLength( i->GetPadDieLength() ); else if( aCol == COLUMN_TOTAL_LENGTH ) aOutValue = m_parent.formatLength( i->GetTotalLength() ); else if( aCol > COLUMN_LAST_STATIC_COL && aCol <= m_parent.m_columns.size() ) aOutValue = m_parent.formatLength( i->GetLayerWireLength( m_parent.m_columns[aCol].layer ) ); else aOutValue = ""; } } static int compareUInt( uint64_t aValue1, uint64_t aValue2, bool aAsc ) { if( aAsc ) return aValue1 < aValue2 ? -1 : 1; else return aValue2 < aValue1 ? -1 : 1; } int Compare( const wxDataViewItem& aItem1, const wxDataViewItem& aItem2, unsigned int aCol, bool aAsc ) const override { const LIST_ITEM& i1 = *static_cast( aItem1.GetID() ); const LIST_ITEM& i2 = *static_cast( aItem2.GetID() ); if( i1.GetIsGroup() && !i2.GetIsGroup() ) return -1; if( i2.GetIsGroup() && !i1.GetIsGroup() ) return 1; if( aCol == COLUMN_NAME ) { const wxString& s1 = i1.GetNetName(); const wxString& s2 = i2.GetNetName(); int res = aAsc ? ValueStringCompare( s1, s2 ) : ValueStringCompare( s2, s1 ); if( res != 0 ) return res; } else if( aCol == COLUMN_PAD_COUNT && i1.GetPadCount() != i2.GetPadCount() ) return compareUInt( i1.GetPadCount(), i2.GetPadCount(), aAsc ); else if( aCol == COLUMN_VIA_COUNT && i1.GetViaCount() != i2.GetViaCount() ) return compareUInt( i1.GetViaCount(), i2.GetViaCount(), aAsc ); else if( aCol == COLUMN_VIA_LENGTH && i1.GetViaLength() != i2.GetViaLength() ) return compareUInt( i1.GetViaLength(), i2.GetViaLength(), aAsc ); else if( aCol == COLUMN_BOARD_LENGTH && i1.GetBoardWireLength() != i2.GetBoardWireLength() ) return compareUInt( i1.GetBoardWireLength(), i2.GetBoardWireLength(), aAsc ); else if( aCol == COLUMN_PAD_DIE_LENGTH && i1.GetPadDieLength() != i2.GetPadDieLength() ) return compareUInt( i1.GetPadDieLength(), i2.GetPadDieLength(), aAsc ); else if( aCol == COLUMN_TOTAL_LENGTH && i1.GetTotalLength() != i2.GetTotalLength() ) return compareUInt( i1.GetTotalLength(), i2.GetTotalLength(), aAsc ); else if( aCol > COLUMN_LAST_STATIC_COL && aCol < m_parent.m_columns.size() && i1.GetLayerWireLength( m_parent.m_columns[aCol].layer ) != i2.GetLayerWireLength( m_parent.m_columns[aCol].layer ) ) { return compareUInt( i1.GetLayerWireLength( m_parent.m_columns[aCol].layer ), i2.GetLayerWireLength( m_parent.m_columns[aCol].layer ), aAsc ); } // when the item values compare equal resort to pointer comparison. wxUIntPtr id1 = wxPtrToUInt( aItem1.GetID() ); wxUIntPtr id2 = wxPtrToUInt( aItem2.GetID() ); return aAsc ? id1 - id2 : id2 - id1; } bool SetValue( const wxVariant& aInValue, const wxDataViewItem& aItem, unsigned int aCol ) override { return false; } wxDataViewItem GetParent( const wxDataViewItem& aItem ) const override { if( !aItem.IsOk() ) return wxDataViewItem(); return wxDataViewItem( static_cast( aItem.GetID() )->Parent() ); } bool IsContainer( const wxDataViewItem& aItem ) const override { if( !aItem.IsOk() ) return true; return static_cast( aItem.GetID() )->GetIsGroup(); } bool HasContainerColumns( const wxDataViewItem& aItem ) const override { return IsContainer( aItem ); } unsigned int GetChildren( const wxDataViewItem& aParent, wxDataViewItemArray& aChildren ) const override { const LIST_ITEM* p = static_cast( aParent.GetID() ); if( !aParent.IsOk() ) { aChildren.Alloc( m_items.size() ); for( const std::unique_ptr& i : m_items ) { if( i->Parent() == nullptr ) aChildren.Add( wxDataViewItem( &*i ) ); } return aChildren.GetCount(); } else if( p->GetIsGroup() ) { const int count = p->ChildrenCount(); if( count == 0 ) return 0; aChildren.Alloc( count ); for( auto i = p->ChildrenBegin(), end = p->ChildrenEnd(); i != end; ++i ) aChildren.Add( wxDataViewItem( *i ) ); return aChildren.GetCount(); } return 0; } wxString GetColumnType( unsigned int /* aCol */ ) const override { return wxS( "string" ); } private: PCB_NET_INSPECTOR_PANEL& m_parent; // primary container, sorted by netcode number. // groups have netcode < 0, so they always come first, in the order // of the filter strings as input by the user std::vector> m_items; /// Map of custom group names to their representative list item std::map m_custom_group_map; }; #endif