/* * This program source code file is part of KiCad, a free EDA CAD application. * * Copyright (C) 2020 Oleg Endo * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr * Copyright (C) 1992-2021 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 2 * 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, you may find one here: * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html * or you may search the http://www.gnu.org website for the version 2 license, * or you may write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct DIALOG_NET_INSPECTOR::COLUMN_DESC { enum { CSV_NONE = 0, CSV_QUOTE = 1 << 0 }; unsigned int num; wxString display_name; wxString csv_name; unsigned int csv_flags; operator unsigned int() const { return num; } }; #define def_col( c, num, name, csv_name, csv_flags ) \ const DIALOG_NET_INSPECTOR::COLUMN_DESC DIALOG_NET_INSPECTOR::c = { num, \ name, \ csv_name, \ COLUMN_DESC::csv_flags } def_col( COLUMN_NET, 0, _( "Net" ), _( "Net Code" ), CSV_NONE ); def_col( COLUMN_NAME, 1, _( "Name" ), _( "Net Name" ), CSV_QUOTE ); def_col( COLUMN_PAD_COUNT, 2, _( "Pad Count" ), _( "Pad Count" ), CSV_NONE ); def_col( COLUMN_VIA_COUNT, 3, _( "Via Count" ), _( "Via Count" ), CSV_NONE ); def_col( COLUMN_VIA_LENGTH, 4, _( "Via Length" ), _( "Via Length" ), CSV_NONE ); def_col( COLUMN_BOARD_LENGTH, 5, _( "Track Length" ), _( "Track Length" ), CSV_NONE ); def_col( COLUMN_CHIP_LENGTH, 6, _( "Die Length" ), _( "Die Length" ), CSV_NONE ); def_col( COLUMN_TOTAL_LENGTH, 7, _( "Total Length" ), _( "Net Length" ), CSV_NONE ); #undef def_col class DIALOG_NET_INSPECTOR::LIST_ITEM { private: // an item can be the child of only one parent at a time. // FIXME: could use a more lightweight container like intrusive forward list. LIST_ITEM* m_parent = nullptr; std::vector m_children; bool m_is_group = false; 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_board_wire_length = 0; uint64_t m_chip_wire_length = 0; // dirty bits to record when some attribute has changed. this is to // avoid unnecessary resort operations. std::bitset<5> m_column_changed; // cached formatted net name for faster display sorting. wxString m_net_name; public: LIST_ITEM( unsigned int aGroupNumber, const wxString& aGroupName ) : m_is_group( true ), m_group_number( aGroupNumber ), m_net_name( aGroupName ) { } LIST_ITEM( NETINFO_ITEM* aNet ) : m_net( aNet ) { m_net_name = UnescapeString( aNet->GetNetname() ); } LIST_ITEM& operator=( const LIST_ITEM& ) = delete; bool GetIsGroup() const { return m_is_group; } 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& GetGroupName() const { return m_net_name; } void ResetColumnChangedBits() { m_column_changed.reset(); } #define gen( mvar, chg_bit, get, set, add, sub, changed ) \ decltype( mvar ) get() const \ { \ return mvar; \ } \ \ bool changed() const \ { \ return m_column_changed[chg_bit]; \ } \ \ void set( const decltype( mvar )& aValue ) \ { \ if( m_parent ) \ m_parent->set( m_parent->get() - mvar + aValue ); \ \ static_assert( chg_bit < decltype( m_column_changed )().size(), "" ); \ m_column_changed[chg_bit] = m_column_changed[chg_bit] | ( mvar != aValue ); \ mvar = aValue; \ } \ \ void add( const decltype( mvar )& aValue ) \ { \ if( m_parent ) \ m_parent->add( aValue ); \ \ static_assert( chg_bit < decltype( m_column_changed )().size(), "" ); \ m_column_changed[chg_bit] = m_column_changed[chg_bit] | ( aValue != 0 ); \ mvar += aValue; \ } \ \ void sub( const decltype( mvar )& aValue ) \ { \ if( m_parent ) \ m_parent->sub( aValue ); \ \ static_assert( chg_bit < decltype( m_column_changed )().size(), "" ); \ m_column_changed[chg_bit] = m_column_changed[chg_bit] | ( aValue != 0 ); \ mvar -= aValue; \ } gen( m_pad_count, 0, GetPadCount, SetPadCount, AddPadCount, SubPadCount, PadCountChanged ); gen( m_via_count, 1, GetViaCount, SetViaCount, AddViaCount, SubViaCount, ViaCountChanged ); gen( m_via_length, 2, GetViaLength, SetViaLength, AddViaLength, SubViaLength, ViaLengthChanged ); gen( m_board_wire_length, 3, GetBoardWireLength, SetBoardWireLength, AddBoardWireLength, SubBoardWireLength, BoardWireLengthChanged ); gen( m_chip_wire_length, 4, GetChipWireLength, SetChipWireLength, AddChipWireLength, SubChipWireLength, ChipWireLengthChanged ); #undef gen // the total length column is always computed, never stored. unsigned long long int GetTotalLength() const { return GetBoardWireLength() + GetViaLength() + GetChipWireLength(); } bool TotalLengthChanged() const { return BoardWireLengthChanged() || ViaLengthChanged() || ChipWireLengthChanged(); } 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() ); m_parent->SubBoardWireLength( GetBoardWireLength() ); m_parent->SubChipWireLength( GetChipWireLength() ); 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() ); m_parent->AddBoardWireLength( GetBoardWireLength() ); m_parent->AddChipWireLength( GetChipWireLength() ); m_parent->m_children.push_back( this ); } } }; struct DIALOG_NET_INSPECTOR::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(); } }; class DIALOG_NET_INSPECTOR::DATA_MODEL : public wxDataViewModel { private: DIALOG_NET_INSPECTOR& 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 (group mode 0, 1) or // in order of occurrence (group mode 2, 3). std::vector> m_items; public: static const auto& columnDesc() { static const std::array r = { { COLUMN_NET, COLUMN_NAME, COLUMN_PAD_COUNT, COLUMN_VIA_COUNT, COLUMN_VIA_LENGTH, COLUMN_BOARD_LENGTH, COLUMN_CHIP_LENGTH, COLUMN_TOTAL_LENGTH } }; return r; } DATA_MODEL( DIALOG_NET_INSPECTOR& parent ) : m_parent( parent ) { } unsigned int columnCount() const { return columnDesc().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 ); } OPT 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 {}; return { i }; } OPT findItem( NETINFO_ITEM* aNet ) { if( aNet != nullptr ) return findItem( aNet->GetNetCode() ); else return {}; } OPT addItem( std::unique_ptr aItem ) { if( aItem == nullptr ) return {}; // make sure that the vector is always sorted. 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; if( m_parent.m_groupBy->IsChecked() && ( m_parent.m_groupByKind->GetSelection() == 0 || m_parent.m_groupByKind->GetSelection() == 1 ) ) { for( unsigned int j = 0; j < m_parent.m_groupFilter.size(); ++j ) { if( m_parent.m_groupFilter[j]->Find( new_item->GetNetName() ) ) { new_item->SetParent( &*m_items[j] ); break; } } } else if( m_parent.m_groupBy->IsChecked() && ( m_parent.m_groupByKind->GetSelection() == 2 || m_parent.m_groupByKind->GetSelection() == 3 ) ) { auto groups_begin = m_items.begin(); auto groups_end = std::find_if_not( m_items.begin(), m_items.end(), []( const std::unique_ptr& x ) { return x->GetIsGroup(); } ); for( std::unique_ptr& f : m_parent.m_groupFilter ) { EDA_PATTERN_MATCH::FIND_RESULT match = f->Find( new_item->GetNetName() ); if( match ) { wxString match_str = new_item->GetNetName().substr( match.start, match.length ); auto group = std::find_if( groups_begin, groups_end, [&]( const std::unique_ptr& x ) { return x->GetNetName() == match_str; } ); if( group == groups_end ) { int dist = std::distance( groups_end, groups_begin ); group = m_items.insert( groups_end, std::make_unique( dist, match_str ) ); groups_end = group + 1; ItemAdded( wxDataViewItem(( *group )->Parent() ), wxDataViewItem( &**group ) ); } new_item->SetParent( &**group ); break; } } } ItemAdded( wxDataViewItem( new_item->Parent() ), wxDataViewItem( new_item.get() ) ); return { new_iter }; } void addItems( std::vector>&& aItems ) { if( m_items.empty() ) { m_items = std::move( aItems ); if( m_parent.m_groupBy->IsChecked() && ( m_parent.m_groupByKind->GetSelection() == 0 || m_parent.m_groupByKind->GetSelection() == 1 ) ) { // assume that there are fewer group filters than nets. // walk over the list items and assign them to groups. note that the // first items are group items themselves, so start after those. for( unsigned int i = m_parent.m_groupFilter.size(); i < m_items.size(); ++i ) { for( unsigned int j = 0; j < m_parent.m_groupFilter.size(); ++j ) { if( m_parent.m_groupFilter[j]->Find( m_items[ i ]->GetNetName() ) ) { m_items[i]->SetParent( &*m_items[j] ); break; } } } } else if( m_parent.m_groupBy->IsChecked() && ( m_parent.m_groupByKind->GetSelection() == 2 || m_parent.m_groupByKind->GetSelection() == 3 ) ) { // assume that there will be fewer resulting groups than nets. // dynamically generate groups for the actual strings of the match result. // try out each filter on each item and group by the resulting substrings. std::vector> groups; for( std::unique_ptr& i : m_items ) { for( std::unique_ptr& f : m_parent.m_groupFilter ) { EDA_PATTERN_MATCH::FIND_RESULT match = f->Find( i->GetNetName() ); if( match ) { wxString match_str = i->GetNetName().substr( match.start, match.length ); auto group = std::find_if( groups.begin(), groups.end(), [&]( const std::unique_ptr& x ) { return x->GetNetName() == match_str; } ); if( group == groups.end() ) { groups.emplace_back( std::make_unique( groups.size(), match_str ) ); group = groups.end() - 1; } i->SetParent( &**group ); break; } } } // insert the group items at the front of the items list. for( std::unique_ptr& g : groups ) m_items.emplace_back( std::move( g ) ); std::rotate( m_items.begin(), m_items.end() - groups.size(), m_items.end() ); } for( std::unique_ptr& i : m_items ) ItemAdded( wxDataViewItem( i->Parent() ), wxDataViewItem( &*i ) ); } else { m_items.reserve( m_items.size() + aItems.size() ); for( std::unique_ptr& i : aItems ) addItem( std::move( i ) ); } } std::unique_ptr deleteItem( const OPT& aRow ) { if( !aRow ) return {}; std::unique_ptr i = std::move( **aRow ); // if the row has a parent, detach it first LIST_ITEM* parent = i->Parent(); i->SetParent( nullptr ); m_items.erase( *aRow ); ItemDeleted( wxDataViewItem( parent ), wxDataViewItem( &*i ) ); if( parent ) { ItemChanged( wxDataViewItem( parent ) ); // for grouping type 2,3 a group item might disappear if it becomes empty. if( ( m_parent.m_groupByKind->GetSelection() == 2 || m_parent.m_groupByKind->GetSelection() == 3 ) && 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; } void deleteAllItems() { BeforeReset(); m_items.clear(); AfterReset(); } void updateItem( const OPT& aRow ) { if( aRow ) { const std::unique_ptr& listItem = *aRow.get(); 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 >= columnDesc().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_CHIP_LENGTH ) return aItem->ChipWireLengthChanged(); else if( aCol == COLUMN_TOTAL_LENGTH ) return aItem->TotalLengthChanged(); 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( aCol == COLUMN_NET && !i->GetIsGroup() ) aOutValue = m_parent.formatNetCode( i->GetNet() ); else if( aCol == COLUMN_NET && i->GetIsGroup() ) aOutValue = ""; else if( aCol == COLUMN_NAME ) aOutValue = i->GetNetName(); 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_CHIP_LENGTH ) aOutValue = m_parent.formatLength( i->GetChipWireLength() ); else if( aCol == COLUMN_TOTAL_LENGTH ) aOutValue = m_parent.formatLength( i->GetTotalLength() ); } } 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_NET && i1.GetNetCode() != i2.GetNetCode() ) { return aAsc ? ( i1.GetNetCode() - i2.GetNetCode() ) : ( i2.GetNetCode() - i1.GetNetCode() ); } else if( aCol == COLUMN_NAME ) { const wxString& s1 = i1.GetNetName(); const wxString& s2 = i2.GetNetName(); int res = aAsc ? s1.Cmp( s2 ) : s2.Cmp( 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_CHIP_LENGTH && i1.GetChipWireLength() != i2.GetChipWireLength() ) return compareUInt( i1.GetChipWireLength(), i2.GetChipWireLength(), aAsc ); else if( aCol == COLUMN_TOTAL_LENGTH && i1.GetTotalLength() != i2.GetTotalLength() ) return compareUInt( i1.GetTotalLength(), i2.GetTotalLength(), 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" ); } }; DIALOG_NET_INSPECTOR::DIALOG_NET_INSPECTOR( PCB_EDIT_FRAME* aParent, const SETTINGS& aSettings ) : DIALOG_NET_INSPECTOR_BASE( aParent ), m_zero_netitem( nullptr ), m_frame( aParent ) { m_brd = aParent->GetBoard(); m_data_model = new DATA_MODEL( *this ); m_netsList->AssociateModel( &*m_data_model ); std::array, 8> add_col = { [&]() { m_netsList->AppendTextColumn( COLUMN_NET.display_name, COLUMN_NET, wxDATAVIEW_CELL_INERT, -1, wxALIGN_LEFT, wxDATAVIEW_COL_SORTABLE ); }, [&]() { m_netsList->AppendTextColumn( COLUMN_NAME.display_name, COLUMN_NAME, wxDATAVIEW_CELL_INERT, -1, wxALIGN_LEFT, wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE ); }, [&]() { m_netsList->AppendTextColumn( COLUMN_PAD_COUNT.display_name, COLUMN_PAD_COUNT, wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE ); }, [&]() { m_netsList->AppendTextColumn( COLUMN_VIA_COUNT.display_name, COLUMN_VIA_COUNT, wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE ); }, [&]() { m_netsList->AppendTextColumn( COLUMN_VIA_LENGTH.display_name, COLUMN_VIA_LENGTH, wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE ); }, [&]() { m_netsList->AppendTextColumn( COLUMN_BOARD_LENGTH.display_name, COLUMN_BOARD_LENGTH, wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE ); }, [&]() { m_netsList->AppendTextColumn( COLUMN_CHIP_LENGTH.display_name, COLUMN_CHIP_LENGTH, wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE ); }, [&]() { m_netsList->AppendTextColumn( COLUMN_TOTAL_LENGTH.display_name, COLUMN_TOTAL_LENGTH, wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER, wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE ); } }; std::vector col_order = aSettings.column_order; if( col_order.size() != add_col.size() ) { col_order.resize( add_col.size() ); for( unsigned int i = 0; i < add_col.size(); ++i ) col_order[i] = i; } for( unsigned int i : col_order ) add_col.at( i )(); m_netsList->SetExpanderColumn( m_netsList->GetColumn( 0 ) ); // avoid onFilterChange for each of the settings as it will re-build the // list over and over again. m_filter_change_no_rebuild = true; m_textCtrlFilter->SetValue( aSettings.filter_string ); m_cbShowZeroPad->SetValue( aSettings.show_zero_pad_nets ); m_groupBy->SetValue( aSettings.group_by ); m_groupByKind->SetSelection( aSettings.group_by_kind ); m_groupByText->SetValue( aSettings.group_by_text ); m_filter_change_no_rebuild = false; buildNetsList(); adjustListColumns(); m_addNet->SetBitmap( KiBitmap( BITMAPS::small_plus ) ); m_renameNet->SetBitmap( KiBitmap( BITMAPS::small_edit ) ); m_deleteNet->SetBitmap( KiBitmap( BITMAPS::small_trash ) ); SetupStandardButtons(); m_renameNet->Disable(); m_deleteNet->Disable(); if( aSettings.sorting_column != -1 ) { if( wxDataViewColumn* c = m_netsList->GetColumn( aSettings.sorting_column ) ) { c->SetSortOrder( aSettings.sort_order_asc ); m_data_model->Resort(); } } finishDialogSettings(); m_frame->Connect( wxEVT_CLOSE_WINDOW, wxCommandEventHandler( DIALOG_NET_INSPECTOR::onParentWindowClosed ), nullptr, this ); m_frame->Connect( UNITS_CHANGED, wxCommandEventHandler( DIALOG_NET_INSPECTOR::onUnitsChanged ), nullptr, this ); m_frame->Connect( BOARD_CHANGED, wxCommandEventHandler( DIALOG_NET_INSPECTOR::onBoardChanged ), nullptr, this ); if( m_brd != nullptr ) { // if the dialog is opened while something is highlighted on the board ... OnBoardHighlightNetChanged( *m_brd ); m_brd->AddListener( this ); } } DIALOG_NET_INSPECTOR::~DIALOG_NET_INSPECTOR() { // the displayed list elements are going to be deleted before the list view itself. // in some cases it might still do queries on the data model, which would crash // from now on. so just disassociate it. m_netsList->AssociateModel( nullptr ); m_frame->Disconnect( wxEVT_CLOSE_WINDOW, wxCommandEventHandler( DIALOG_NET_INSPECTOR::onParentWindowClosed ), nullptr, this ); m_frame->Disconnect( UNITS_CHANGED, wxCommandEventHandler( DIALOG_NET_INSPECTOR::onUnitsChanged ), nullptr, this ); m_frame->Disconnect( BOARD_CHANGED, wxCommandEventHandler( DIALOG_NET_INSPECTOR::onBoardChanged ), nullptr, this ); if( m_brd != nullptr ) m_brd->RemoveListener( this ); m_frame->GetCanvas()->SetFocus(); } DIALOG_NET_INSPECTOR::SETTINGS DIALOG_NET_INSPECTOR::Settings() const { std::vector column_order( m_data_model->columnCount() ); for( unsigned int i = 0; i < column_order.size(); ++i ) column_order[i] = m_netsList->GetColumn( i )->GetModelColumn(); wxDataViewColumn* sorting_column = m_netsList->GetSortingColumn(); SETTINGS r; r.filter_string = m_textCtrlFilter->GetValue(); r.show_zero_pad_nets = m_cbShowZeroPad->IsChecked(); r.group_by = m_groupBy->IsChecked(); r.group_by_kind = m_groupByKind->GetSelection(); r.group_by_text = m_groupByText->GetValue(); r.sorting_column = sorting_column ? static_cast( sorting_column->GetModelColumn() ) : -1; r.sort_order_asc = sorting_column ? sorting_column->IsSortOrderAscending() : true; r.column_order = column_order; return r; } void DIALOG_NET_INSPECTOR::onParentWindowClosed( wxCommandEvent& event ) { Close(); event.Skip(); } void DIALOG_NET_INSPECTOR::onUnitsChanged( wxCommandEvent& event ) { this->m_units = m_frame->GetUserUnits(); m_data_model->updateAllItems(); event.Skip(); } void DIALOG_NET_INSPECTOR::onBoardChanged( wxCommandEvent& event ) { m_brd = m_frame->GetBoard(); if( m_brd != nullptr ) m_brd->AddListener( this ); buildNetsList(); m_netsList->Refresh(); event.Skip(); } bool DIALOG_NET_INSPECTOR::netFilterMatches( NETINFO_ITEM* aNet ) const { // Note: the filtering is case insensitive. // Never show the unconnected net if( aNet->GetNetCode() <= 0 ) return false; // Show unconnected nets only if specifically asked for by filter if( m_netFilter.empty() ) return !aNet->GetNetname().StartsWith( wxT( "unconnected-(" ) ); wxString net_str = UnescapeString( aNet->GetNetname() ).Upper(); for( const std::unique_ptr& f : m_netFilter ) { if( f->Find( net_str ) ) return true; } return false; } struct NETCODE_CMP_LESS { bool operator()( const CN_ITEM* a, const CN_ITEM* b ) const { return a->Net() < b->Net(); } bool operator()( const CN_ITEM* a, int b ) const { return a->Net() < b; } bool operator()( int a, const CN_ITEM* b ) const { return a < b->Net(); } }; std::vector DIALOG_NET_INSPECTOR::relevantConnectivityItems() const { // pre-filter the connectivity items and sort them by netcode. // this avoids quadratic runtime when building the whole net list and // calculating the total length for each net. const auto type_bits = std::bitset() .set( PCB_TRACE_T ) .set( PCB_ARC_T ) .set( PCB_VIA_T ) .set( PCB_PAD_T ); std::vector cn_items; cn_items.reserve( 1024 ); for( CN_ITEM* cn_item : m_brd->GetConnectivity()->GetConnectivityAlgo()->ItemList() ) { if( cn_item->Valid() && type_bits[cn_item->Parent()->Type()] ) cn_items.push_back( cn_item ); } std::sort( cn_items.begin(), cn_items.end(), NETCODE_CMP_LESS() ); return cn_items; } void DIALOG_NET_INSPECTOR::updateDisplayedRowValues( const OPT& aRow ) { if( !aRow ) return; wxDataViewItemArray sel; m_netsList->GetSelections( sel ); m_data_model->updateItem( aRow ); if( !sel.IsEmpty() ) { m_netsList->SetSelections( sel ); m_netsList->EnsureVisible( sel.Item( 0 ) ); } } wxString DIALOG_NET_INSPECTOR::formatNetCode( const NETINFO_ITEM* aNet ) const { return wxString::Format( wxT( "%.3d" ), aNet->GetNetCode() ); } wxString DIALOG_NET_INSPECTOR::formatNetName( const NETINFO_ITEM* aNet ) const { return UnescapeString( aNet->GetNetname() ); } wxString DIALOG_NET_INSPECTOR::formatCount( unsigned int aValue ) const { return wxString::Format( wxT( "%u" ), aValue ); } wxString DIALOG_NET_INSPECTOR::formatLength( int64_t aValue ) const { return MessageTextFromValue( GetUserUnits(), static_cast( aValue ) ); } void DIALOG_NET_INSPECTOR::OnBoardItemAdded( BOARD& aBoard, BOARD_ITEM* aBoardItem ) { if( NETINFO_ITEM* net = dynamic_cast( aBoardItem ) ) { // a new net has been added to the board. add it to our list if it // passes the netname filter test. if( netFilterMatches( net ) ) { std::unique_ptr new_item = std::make_unique( net ); // the new net could have some pads already assigned, count them. new_item->SetPadCount( m_brd->GetNodesCount( net->GetNetCode() ) ); m_data_model->addItem( std::move( new_item ) ); } } else if( BOARD_CONNECTED_ITEM* i = dynamic_cast( aBoardItem ) ) { OPT r = m_data_model->findItem( i->GetNet() ); if( r ) { // try to handle frequent operations quickly. if( PCB_TRACK* track = dynamic_cast( i ) ) { const std::unique_ptr& list_item = *r.get(); int len = track->GetLength(); list_item->AddBoardWireLength( len ); if( track->Type() == PCB_VIA_T ) { list_item->AddViaCount( 1 ); list_item->AddViaLength( calculateViaLength( track ) ); } updateDisplayedRowValues( r ); return; } } // resort to generic slower net update otherwise. updateNet( i->GetNet() ); } else if( FOOTPRINT* footprint = dynamic_cast( aBoardItem ) ) { for( const PAD* pad : footprint->Pads() ) { OPT r = m_data_model->findItem( pad->GetNet() ); if( !r ) { // if show-zero-pads is off, we might not have this net // in our list yet, so add it first. // notice that at this point we are very certain that this net // will have at least one pad. if( netFilterMatches( pad->GetNet() ) ) r = m_data_model->addItem( std::make_unique( pad->GetNet() ) ); } if( r ) { const std::unique_ptr& list_item = *r.get(); int len = pad->GetPadToDieLength(); list_item->AddPadCount( 1 ); list_item->AddChipWireLength( len ); if( list_item->GetPadCount() == 0 && !m_cbShowZeroPad->IsChecked() ) m_data_model->deleteItem( r ); else updateDisplayedRowValues( r ); } } } } void DIALOG_NET_INSPECTOR::OnBoardItemsAdded( BOARD& aBoard, std::vector& aBoardItem ) { for( BOARD_ITEM* item : aBoardItem ) { OnBoardItemAdded( aBoard, item ); } } void DIALOG_NET_INSPECTOR::OnBoardItemRemoved( BOARD& aBoard, BOARD_ITEM* aBoardItem ) { if( NETINFO_ITEM* net = dynamic_cast( aBoardItem ) ) { m_data_model->deleteItem( m_data_model->findItem( net ) ); } else if( FOOTPRINT* footprint = dynamic_cast( aBoardItem ) ) { for( const PAD* pad : footprint->Pads() ) { OPT r = m_data_model->findItem( pad->GetNet() ); if( r ) { const std::unique_ptr& list_item = *r.get(); int len = pad->GetPadToDieLength(); list_item->SubPadCount( 1 ); list_item->SubChipWireLength( len ); if( list_item->GetPadCount() == 0 && !m_cbShowZeroPad->IsChecked() ) m_data_model->deleteItem( r ); else updateDisplayedRowValues( r ); } } } else if( BOARD_CONNECTED_ITEM* i = dynamic_cast( aBoardItem ) ) { OPT r = m_data_model->findItem( i->GetNet() ); if( r ) { // try to handle frequent operations quickly. if( PCB_TRACK* track = dynamic_cast( i ) ) { const std::unique_ptr& list_item = *r.get(); int len = track->GetLength(); list_item->SubBoardWireLength( len ); if( track->Type() == PCB_VIA_T ) { list_item->SubViaCount( 1 ); list_item->SubViaLength( calculateViaLength( track ) ); } updateDisplayedRowValues( r ); return; } // resort to generic slower net update otherwise. updateNet( i->GetNet() ); } } } void DIALOG_NET_INSPECTOR::OnBoardItemsRemoved( BOARD& aBoard, std::vector& aBoardItems ) { for( BOARD_ITEM* item : aBoardItems ) { OnBoardItemRemoved( aBoard, item ); } } void DIALOG_NET_INSPECTOR::OnBoardItemChanged( BOARD& aBoard, BOARD_ITEM* aBoardItem ) { if( dynamic_cast( aBoardItem ) != nullptr || dynamic_cast( aBoardItem ) != nullptr ) { buildNetsList(); m_netsList->Refresh(); } } void DIALOG_NET_INSPECTOR::OnBoardItemsChanged( BOARD& aBoard, std::vector& aBoardItems ) { buildNetsList(); m_netsList->Refresh(); } void DIALOG_NET_INSPECTOR::OnBoardHighlightNetChanged( BOARD& aBoard ) { if( !m_brd->IsHighLightNetON() ) { m_netsList->UnselectAll(); } else { const std::set& selected_codes = m_brd->GetHighLightNetCodes(); wxDataViewItemArray new_selection; new_selection.Alloc( selected_codes.size() ); for( int code : selected_codes ) { if( OPT r = m_data_model->findItem( code ) ) new_selection.Add( wxDataViewItem( &***r ) ); } m_netsList->SetSelections( new_selection ); if( !new_selection.IsEmpty() ) m_netsList->EnsureVisible( new_selection.Item( 0 ) ); } } void DIALOG_NET_INSPECTOR::OnBoardNetSettingsChanged( BOARD& aBoard ) { buildNetsList(); m_netsList->Refresh(); } void DIALOG_NET_INSPECTOR::updateNet( NETINFO_ITEM* aNet ) { // something for the specified net has changed, update that row. // ignore nets that are not in our list because the filter doesn't match. if( !netFilterMatches( aNet ) ) { m_data_model->deleteItem( m_data_model->findItem( aNet ) ); return; } // if the net had no pads before, it might not be in the displayed list yet. // if it had pads and now doesn't anymore, we might need to remove it from the list. OPT cur_net_row = m_data_model->findItem( aNet ); const unsigned int node_count = m_brd->GetNodesCount( aNet->GetNetCode() ); if( node_count == 0 && !m_cbShowZeroPad->IsChecked() ) { m_data_model->deleteItem( cur_net_row ); return; } std::unique_ptr new_list_item = buildNewItem( aNet, node_count, relevantConnectivityItems() ); if( !cur_net_row ) { m_data_model->addItem( std::move( new_list_item ) ); return; } const std::unique_ptr& cur_list_item = *cur_net_row.get(); if( cur_list_item->GetNetName() != new_list_item->GetNetName() ) { // if the name has changed, it might require re-grouping. // it's easier to remove and re-insert it m_data_model->deleteItem( cur_net_row ); m_data_model->addItem( std::move( new_list_item ) ); } else { // update fields only cur_list_item->SetPadCount( new_list_item->GetPadCount() ); cur_list_item->SetViaCount( new_list_item->GetViaCount() ); cur_list_item->SetBoardWireLength( new_list_item->GetBoardWireLength() ); cur_list_item->SetChipWireLength( new_list_item->GetChipWireLength() ); updateDisplayedRowValues( cur_net_row ); } } unsigned int DIALOG_NET_INSPECTOR::calculateViaLength( const PCB_TRACK* aTrack ) const { const PCB_VIA& via = dynamic_cast( *aTrack ); BOARD_DESIGN_SETTINGS& bds = m_brd->GetDesignSettings(); // calculate the via length individually from the board stackup and via's start and end layer. if( bds.m_HasStackup ) { const BOARD_STACKUP& stackup = bds.GetStackupDescriptor(); return stackup.GetLayerDistance( via.TopLayer(), via.BottomLayer() ); } else { int dielectricLayers = bds.GetCopperLayerCount() - 1; // FIXME: not all dielectric layers are the same thickness! int layerThickness = bds.GetBoardThickness() / dielectricLayers; int effectiveBottomLayer; if( via.BottomLayer() == B_Cu ) effectiveBottomLayer = F_Cu + dielectricLayers; else effectiveBottomLayer = via.BottomLayer(); int layerCount = effectiveBottomLayer - via.TopLayer(); return layerCount * layerThickness; } } std::unique_ptr DIALOG_NET_INSPECTOR::buildNewItem( NETINFO_ITEM* aNet, unsigned int aPadCount, const std::vector& aCNItems ) { std::unique_ptr new_item = std::make_unique( aNet ); new_item->SetPadCount( aPadCount ); const auto cn_items = std::equal_range( aCNItems.begin(), aCNItems.end(), aNet->GetNetCode(), NETCODE_CMP_LESS() ); for( auto i = cn_items.first; i != cn_items.second; ++i ) { BOARD_CONNECTED_ITEM* item = ( *i )->Parent(); if( item->Type() == PCB_PAD_T ) new_item->AddChipWireLength( static_cast( item )->GetPadToDieLength() ); else if( PCB_TRACK* track = dynamic_cast( item ) ) { new_item->AddBoardWireLength( track->GetLength() ); if( item->Type() == PCB_VIA_T ) { new_item->AddViaCount( 1 ); new_item->AddViaLength( calculateViaLength( track ) ); } } } return new_item; } void DIALOG_NET_INSPECTOR::buildNetsList() { // Only build the list of nets if there is a board present if( !m_brd ) return; m_in_build_nets_list = true; // when rebuilding the netlist, try to keep the row selection // FIXME: handle group selections, preserve expanded/collapsed group states wxDataViewItemArray sel; m_netsList->GetSelections( sel ); std::vector prev_selected_netcodes; prev_selected_netcodes.reserve( sel.GetCount() ); for( unsigned int i = 0; i < sel.GetCount(); ++i ) { const LIST_ITEM* item = static_cast( sel.Item( i ).GetID() ); prev_selected_netcodes.push_back( item->GetNetCode() ); } m_data_model->deleteAllItems(); std::vector> new_items; // for group mode 0,1 each group filter string represents one displayed group, // so just add them first. for group mode 2,3 the groups are generated dynamically. if( m_groupBy->IsChecked() && ( m_groupByKind->GetSelection() == 0 || m_groupByKind->GetSelection() == 1 ) ) { for( unsigned int i = 0; i < m_groupFilter.size(); ++i ) { const std::unique_ptr& filter = m_groupFilter[i]; new_items.emplace_back( std::make_unique( i, filter->GetPattern() ) ); } } std::vector prefiltered_cn_items = relevantConnectivityItems(); // collect all nets which pass the filter string and also remember the // suffix after the filter match, if any. struct NET_INFO { int netcode; NETINFO_ITEM* net; unsigned int pad_count; }; struct NET_INFO_CMP_LESS { bool operator()( const NET_INFO& a, const NET_INFO& b ) const { return a.netcode < b.netcode; } bool operator()( const NET_INFO& a, int b ) const { return a.netcode < b; } bool operator()( int a, const NET_INFO& b ) const { return a < b.netcode; } }; std::vector nets; nets.reserve( m_brd->GetNetInfo().NetsByNetcode().size() ); for( const std::pair ni : m_brd->GetNetInfo().NetsByNetcode() ) { if( ni.first == 0 ) m_zero_netitem = ni.second; if( netFilterMatches( ni.second ) ) nets.emplace_back( NET_INFO{ ni.first, ni.second, 0 } ); } // count the pads for each net. since the nets are sorted by netcode // iterating over the footprints' pads is faster. for( FOOTPRINT* footprint : m_brd->Footprints() ) { for( PAD* pad : footprint->Pads() ) { auto i = std::lower_bound( nets.begin(), nets.end(), pad->GetNetCode(), NET_INFO_CMP_LESS() ); if( i != nets.end() && i->netcode == pad->GetNetCode() ) i->pad_count += 1; } } for( NET_INFO& ni : nets ) { if( m_cbShowZeroPad->IsChecked() || ni.pad_count > 0 ) new_items.emplace_back( buildNewItem( ni.net, ni.pad_count, prefiltered_cn_items ) ); } m_data_model->addItems( std::move( new_items ) ); // try to restore the selected rows. set the ones that we can't find anymore to -1. sel.Clear(); for( int& nc : prev_selected_netcodes ) { auto r = m_data_model->findItem( nc ); if( r ) { const std::unique_ptr& list_item = *r.get(); sel.Add( wxDataViewItem( list_item.get() ) ); } else { nc = -1; } } if( !sel.IsEmpty() ) { m_netsList->SetSelections( sel ); m_netsList->EnsureVisible( sel.Item( 0 ) ); } else { m_netsList->UnselectAll(); } alg::delete_matching( prev_selected_netcodes, -1 ); m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings()->SetHighlight( false ); for( int& i : prev_selected_netcodes ) m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings()->SetHighlight( true, i, true ); m_frame->GetCanvas()->GetView()->UpdateAllLayersColor(); m_frame->GetCanvas()->Refresh(); m_in_build_nets_list = false; } void DIALOG_NET_INSPECTOR::onFilterChange( wxCommandEvent& aEvent ) { wxStringTokenizer filters( m_textCtrlFilter->GetValue().Upper(), wxT( "," ) ); m_netFilter.clear(); while( filters.HasMoreTokens() ) { wxString t = filters.GetNextToken(); t.Trim( false ); t.Trim( true ); if( !t.IsEmpty() ) { m_netFilter.emplace_back( std::make_unique() ); m_netFilter.back()->SetPattern( t ); } } wxStringTokenizer group_filters( m_groupByText->GetValue(), wxT( "," ) ); m_groupFilter.clear(); while( group_filters.HasMoreTokens() ) { wxString t = group_filters.GetNextToken(); t.Trim( false ); t.Trim( true ); if( !t.IsEmpty() ) { if( m_groupByKind->GetSelection() == 0 || m_groupByKind->GetSelection() == 2 ) { // type 2: wildcard match, use the matching substring as a group key. // the number of groups is determined dynamically by the number of // resulting matches in the whole set. m_groupFilter.emplace_back( std::make_unique() ); m_groupFilter.back()->SetPattern( t ); } else if( m_groupByKind->GetSelection() == 1 || m_groupByKind->GetSelection() == 3 ) { // type 3: regex match, use the matching substring as a group key. // the number of groups is determined dynamically by the number of // resulting matches in the whole set. m_groupFilter.emplace_back( std::make_unique() ); m_groupFilter.back()->SetPattern( t ); } } } if( !m_filter_change_no_rebuild ) buildNetsList(); } void DIALOG_NET_INSPECTOR::onSelChanged( wxDataViewEvent& ) { onSelChanged(); } void DIALOG_NET_INSPECTOR::onSelChanged() { // ignore selection changes while the whole list is being rebuilt. if( m_in_build_nets_list ) return; RENDER_SETTINGS* ps = m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings(); bool enable_rename_button = false; bool enable_delete_button = false; if( m_netsList->HasSelection() ) { wxDataViewItemArray sel; m_netsList->GetSelections( sel ); ps->SetHighlight( false ); enable_rename_button = sel.GetCount() == 1; enable_delete_button = true; for( unsigned int i = 0; i < sel.GetCount(); ++i ) { const LIST_ITEM* ii = static_cast( sel.Item( i ).GetID() ); if( ii->GetIsGroup() ) { enable_rename_button = false; for( auto c = ii->ChildrenBegin(), end = ii->ChildrenEnd(); c != end; ++c ) ps->SetHighlight( true, ( *c )->GetNetCode(), true ); } else ps->SetHighlight( true, ii->GetNetCode(), true ); } } else ps->SetHighlight( false ); m_frame->GetCanvas()->GetView()->UpdateAllLayersColor(); m_frame->GetCanvas()->Refresh(); m_renameNet->Enable( enable_rename_button ); m_deleteNet->Enable( enable_delete_button ); } void DIALOG_NET_INSPECTOR::onSortingChanged( wxDataViewEvent& aEvent ) { // FIXME: Whenever the sort criteria changes (sorting column) // the visible row-numers of the selection get preserved, not the actual // elements. Don't know at the moment how to preserve the selection, // so just clear it for now. m_netsList->UnselectAll(); m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings()->SetHighlight( false ); m_frame->GetCanvas()->GetView()->UpdateAllLayersColor(); m_frame->GetCanvas()->Refresh(); } void DIALOG_NET_INSPECTOR::adjustListColumns() { wxWindowUpdateLocker locker( m_netsList ); int w0 = GetTextExtent( COLUMN_NET.display_name ).x; int w1 = GetTextExtent( COLUMN_NAME.display_name ).x; int w2 = GetTextExtent( COLUMN_PAD_COUNT.display_name ).x; int w3 = GetTextExtent( COLUMN_VIA_COUNT.display_name ).x; int w4 = GetTextExtent( COLUMN_VIA_LENGTH.display_name ).x; int w5 = GetTextExtent( COLUMN_BOARD_LENGTH.display_name ).x; int w6 = GetTextExtent( COLUMN_CHIP_LENGTH.display_name ).x; int w7 = GetTextExtent( COLUMN_TOTAL_LENGTH.display_name ).x; int minValueWidth = GetTextExtent( wxT( "00000,000 mm" ) ).x; int minNumberWidth = GetTextExtent( wxT( "000" ) ).x; int minNameWidth = GetTextExtent( wxT( "MMMMMM" ) ).x; // Considering left and right margins. // For wxRenderGeneric it is 5px. // Also account for the sorting arrow in the column header. // Column 0 also needs space for any potential expander icons. const int margins = 15; const int extra_width = 30; w0 = std::max( w0, minNumberWidth ) + extra_width; w1 = std::max( w1, minNameWidth ) + margins; w2 = std::max( w2, minNumberWidth ) + margins; w3 = std::max( w3, minNumberWidth ) + margins; w4 = std::max( w4, minValueWidth ) + margins; w5 = std::max( w5, minValueWidth ) + margins; w6 = std::max( w6, minValueWidth ) + margins; w7 = std::max( w7, minValueWidth ) + margins; // the columns might have been reordered. we work on the column model numbers though. std::vector column_order( m_data_model->columnCount() ); for( unsigned int i = 0; i < column_order.size(); ++i ) column_order[m_netsList->GetColumn( i )->GetModelColumn()] = i; assert( column_order.size() == 8 ); m_netsList->GetColumn( column_order[0] )->SetMinWidth( w0 ); m_netsList->GetColumn( column_order[1] )->SetMinWidth( w1 ); m_netsList->GetColumn( column_order[2] )->SetMinWidth( w2 ); m_netsList->GetColumn( column_order[3] )->SetMinWidth( w3 ); m_netsList->GetColumn( column_order[4] )->SetMinWidth( w4 ); m_netsList->GetColumn( column_order[5] )->SetMinWidth( w5 ); m_netsList->GetColumn( column_order[6] )->SetMinWidth( w6 ); m_netsList->GetColumn( column_order[7] )->SetMinWidth( w7 ); // At resizing of the list the width of middle column (Net Names) changes only. int width = KIPLATFORM::UI::GetUnobscuredSize( m_netsList ).x; int remaining = width - w0 - w2 - w3 - w4 - w5 - w6 - w7; if( remaining > w1 ) m_netsList->GetColumn( column_order[1] )->SetWidth( remaining ); m_netsList->Refresh(); // Force refresh on GTK so that horizontal scroll bar won't appear #ifdef __WXGTK__ wxPoint pos = m_netsList->GetPosition(); m_netsList->Move( pos.x, pos.y + 1 ); m_netsList->Move( pos.x, pos.y ); #endif } void DIALOG_NET_INSPECTOR::onListSize( wxSizeEvent& aEvent ) { auto new_size = aEvent.GetSize(); if( new_size != m_size ) { m_size = new_size; adjustListColumns(); } aEvent.Skip(); } void DIALOG_NET_INSPECTOR::onAddNet( wxCommandEvent& aEvent ) { wxString newNetName; NETNAME_VALIDATOR validator( &newNetName ); WX_TEXT_ENTRY_DIALOG dlg( this, _( "Net name:" ), _( "New Net" ), newNetName ); dlg.SetTextValidator( validator ); while( true ) { if( dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty() ) return; //Aborted by user newNetName = dlg.GetValue(); if( m_brd->FindNet( newNetName ) ) { DisplayError( this, wxString::Format( _( "Net name '%s' is already in use." ), newNetName ) ); newNetName = wxEmptyString; } else { break; } } NETINFO_ITEM *newnet = new NETINFO_ITEM( m_brd, dlg.GetValue(), 0 ); m_brd->Add( newnet ); m_frame->OnModify(); // We'll get an OnBoardItemAdded callback from this to update our listbox } void DIALOG_NET_INSPECTOR::onRenameNet( wxCommandEvent& aEvent ) { if( m_netsList->GetSelectedItemsCount() == 1 ) { const LIST_ITEM* sel = static_cast( m_netsList->GetSelection().GetID() ); if( sel->GetIsGroup() ) return; NETINFO_ITEM* net = sel->GetNet(); wxString fullNetName = net->GetNetname(); wxString netPath; wxString shortNetName; if( fullNetName.Contains( wxT( "/" ) ) ) { netPath = fullNetName.BeforeLast( '/' ) + '/'; shortNetName = fullNetName.AfterLast( '/' ); } else { shortNetName = fullNetName; } wxString unescapedShortName = UnescapeString( shortNetName ); WX_TEXT_ENTRY_DIALOG dlg( this, _( "Net name:" ), _( "Rename Net" ), unescapedShortName ); NETNAME_VALIDATOR validator( &unescapedShortName ); dlg.SetTextValidator( validator ); while( true ) { if( dlg.ShowModal() != wxID_OK || dlg.GetValue() == unescapedShortName ) return; unescapedShortName = dlg.GetValue(); if( unescapedShortName.IsEmpty() ) { DisplayError( this, wxString::Format( _( "Net name cannot be empty." ), unescapedShortName ) ); continue; } shortNetName = EscapeString( unescapedShortName, CTX_NETNAME ); fullNetName = netPath + shortNetName; if( m_brd->FindNet( shortNetName ) || m_brd->FindNet( fullNetName ) ) { DisplayError( this, wxString::Format( _( "Net name '%s' is already in use." ), unescapedShortName ) ); unescapedShortName = wxEmptyString; } else { break; } } // the changed name might require re-grouping. remove and re-insert // is easier. auto removed_item = m_data_model->deleteItem( m_data_model->findItem( net ) ); m_brd->GetNetInfo().RemoveNet( net ); net->SetNetname( fullNetName ); m_brd->GetNetInfo().AppendNet( net ); m_frame->OnModify(); if( netFilterMatches( net ) ) { std::unique_ptr new_item = std::make_unique( net ); new_item->SetPadCount( removed_item->GetPadCount() ); new_item->SetViaCount( removed_item->GetViaCount() ); new_item->SetBoardWireLength( removed_item->GetBoardWireLength() ); new_item->SetChipWireLength( removed_item->GetChipWireLength() ); OPT added_row = m_data_model->addItem( std::move( new_item ) ); wxDataViewItemArray new_sel; new_sel.Add( wxDataViewItem( &***added_row ) ); m_netsList->SetSelections( new_sel ); onSelChanged(); } // Currently only tracks and pads have netname annotations and need to be redrawn, // but zones are likely to follow. Since we don't have a way to ask what is current, // just refresh all items. m_frame->GetCanvas()->GetView()->UpdateAllItems( KIGFX::REPAINT ); m_frame->GetCanvas()->Refresh(); } } void DIALOG_NET_INSPECTOR::onDeleteNet( wxCommandEvent& aEvent ) { if( !m_netsList->HasSelection() ) return; wxDataViewItemArray sel; m_netsList->GetSelections( sel ); auto delete_one = [this]( const LIST_ITEM* i ) { if( i->GetPadCount() == 0 || IsOK( this, wxString::Format( _( "Net '%s' is in use. Delete anyway?" ), i->GetNetName() ) ) ) { // This is a bit hacky, but it will do for now, since this is the only path // outside the netlist updater where you can remove a net from a BOARD. int removedCode = i->GetNetCode(); m_frame->GetCanvas()->GetView()->UpdateAllItemsConditionally( KIGFX::REPAINT, [removedCode]( KIGFX::VIEW_ITEM* aItem ) -> bool { if( auto bci = dynamic_cast( aItem ) ) return bci->GetNetCode() == removedCode; return false; } ); m_brd->Remove( i->GetNet() ); m_frame->OnModify(); // We'll get an OnBoardItemRemoved callback from this to update our listbox } }; for( unsigned int i = 0; i < sel.GetCount(); ++i ) { const LIST_ITEM* ii = static_cast( sel.Item( i ).GetID() ); if( ii->GetIsGroup() ) { if( ii->ChildrenCount() != 0 && IsOK( this, wxString::Format( _( "Delete all nets in group '%s'?" ), ii->GetGroupName() ) ) ) { // we can't be iterating the children container and deleting items from // it at the same time. thus take a copy of it first. std::vector children; children.reserve( ii->ChildrenCount() ); std::copy( ii->ChildrenBegin(), ii->ChildrenEnd(), std::back_inserter( children ) ); for( const LIST_ITEM* c : children ) delete_one( c ); } } else { delete_one( ii ); } } } void DIALOG_NET_INSPECTOR::onReport( wxCommandEvent& aEvent ) { wxFileDialog dlg( this, _( "Report file" ), "", "", _( "Report file" ) + AddFileExtListToFilter( { "csv" } ), wxFD_SAVE ); if( dlg.ShowModal() == wxID_CANCEL ) return; wxTextFile f( dlg.GetPath() ); f.Create(); wxString txt; // Print Header: for( auto&& col : m_data_model->columnDesc() ) txt += '"' + col.csv_name + wxT( "\";" ); f.AddLine( txt ); // Print list of nets: const unsigned int num_rows = m_data_model->itemCount(); for( unsigned int row = 0; row < num_rows; row++ ) { auto& i = m_data_model->itemAt( row ); if( i.GetIsGroup() || i.GetNetCode() == 0 ) continue; txt = ""; for( auto&& col : m_data_model->columnDesc() ) { if( col.csv_flags & COLUMN_DESC::CSV_QUOTE ) txt += '"' + m_data_model->valueAt( col.num, row ).GetString() + wxT( "\";" ); else txt += m_data_model->valueAt( col.num, row ).GetString() + ';'; } f.AddLine( txt ); } f.Write(); f.Close(); }