You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2015 lines
67 KiB

5 years ago
5 years ago
5 years ago
5 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2020 Oleg Endo <olegendo@gcc.gnu.org>
  5. * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
  6. * Copyright (C) 1992-2021 KiCad Developers, see AUTHORS.txt for contributors.
  7. *
  8. * This program is free software; you can redistribute it and/or
  9. * modify it under the terms of the GNU General Public License
  10. * as published by the Free Software Foundation; either version 2
  11. * of the License, or (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU General Public License
  19. * along with this program; if not, you may find one here:
  20. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  21. * or you may search the http://www.gnu.org website for the version 2 license,
  22. * or you may write to the Free Software Foundation, Inc.,
  23. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  24. */
  25. #include <string_utils.h>
  26. #include <tools/board_inspection_tool.h>
  27. #include <board.h>
  28. #include <board_design_settings.h>
  29. #include <confirm.h>
  30. #include <pcb_track.h>
  31. #include <dialog_net_inspector.h>
  32. #include <eda_pattern_match.h>
  33. #include <wildcards_and_files_ext.h>
  34. #include <view/view.h>
  35. #include <view/view_controls.h>
  36. #include <pcb_painter.h>
  37. #include <connectivity/connectivity_data.h>
  38. #include <connectivity/connectivity_algo.h>
  39. #include <dialogs/dialog_text_entry.h>
  40. #include <validators.h>
  41. #include <bitmaps.h>
  42. #include <wx/tokenzr.h>
  43. #include <wx/filedlg.h>
  44. #include <wx/dcclient.h>
  45. #include <bitset>
  46. struct DIALOG_NET_INSPECTOR::COLUMN_DESC
  47. {
  48. enum
  49. {
  50. CSV_NONE = 0,
  51. CSV_QUOTE = 1 << 0
  52. };
  53. unsigned int num;
  54. wxString display_name;
  55. wxString csv_name;
  56. unsigned int csv_flags;
  57. operator unsigned int() const
  58. {
  59. return num;
  60. }
  61. };
  62. #define def_col( c, num, name, csv_name, csv_flags ) \
  63. const DIALOG_NET_INSPECTOR::COLUMN_DESC DIALOG_NET_INSPECTOR::c = { num, \
  64. name, \
  65. csv_name, \
  66. COLUMN_DESC::csv_flags }
  67. def_col( COLUMN_NET, 0, _( "Net" ), _( "Net Code" ), CSV_NONE );
  68. def_col( COLUMN_NAME, 1, _( "Name" ), _( "Net Name" ), CSV_QUOTE );
  69. def_col( COLUMN_PAD_COUNT, 2, _( "Pad Count" ), _( "Pad Count" ), CSV_NONE );
  70. def_col( COLUMN_VIA_COUNT, 3, _( "Via Count" ), _( "Via Count" ), CSV_NONE );
  71. def_col( COLUMN_VIA_LENGTH, 4, _( "Via Length" ), _( "Via Length" ), CSV_NONE );
  72. def_col( COLUMN_BOARD_LENGTH, 5, _( "Track Length" ), _( "Track Length" ), CSV_NONE );
  73. def_col( COLUMN_CHIP_LENGTH, 6, _( "Die Length" ), _( "Die Length" ), CSV_NONE );
  74. def_col( COLUMN_TOTAL_LENGTH, 7, _( "Total Length" ), _( "Net Length" ), CSV_NONE );
  75. #undef def_col
  76. class DIALOG_NET_INSPECTOR::LIST_ITEM
  77. {
  78. private:
  79. // an item can be the child of only one parent at a time.
  80. // FIXME: could use a more lightweight container like intrusive forward list.
  81. LIST_ITEM* m_parent = nullptr;
  82. std::vector<LIST_ITEM*> m_children;
  83. bool m_is_group = false;
  84. unsigned int m_group_number = 0;
  85. NETINFO_ITEM* m_net = nullptr;
  86. unsigned int m_pad_count = 0;
  87. unsigned int m_via_count = 0;
  88. uint64_t m_via_length = 0;
  89. uint64_t m_board_wire_length = 0;
  90. uint64_t m_chip_wire_length = 0;
  91. // dirty bits to record when some attribute has changed. this is to
  92. // avoid unnecessary resort operations.
  93. std::bitset<5> m_column_changed;
  94. // cached formatted net name for faster display sorting.
  95. wxString m_net_name;
  96. public:
  97. LIST_ITEM( unsigned int aGroupNumber, const wxString& aGroupName ) :
  98. m_is_group( true ),
  99. m_group_number( aGroupNumber ),
  100. m_net_name( aGroupName )
  101. {
  102. }
  103. LIST_ITEM( NETINFO_ITEM* aNet ) :
  104. m_net( aNet )
  105. {
  106. m_net_name = UnescapeString( aNet->GetNetname() );
  107. }
  108. LIST_ITEM& operator=( const LIST_ITEM& ) = delete;
  109. bool GetIsGroup() const { return m_is_group; }
  110. auto ChildrenBegin() const { return m_children.begin(); }
  111. auto ChildrenEnd() const { return m_children.end(); }
  112. unsigned int ChildrenCount() const { return m_children.size(); }
  113. NETINFO_ITEM* GetNet() const { return m_net; }
  114. int GetNetCode() const
  115. {
  116. return GetIsGroup() ? ( 0 - int( m_group_number ) - 1 ) : m_net->GetNetCode();
  117. }
  118. const wxString& GetNetName() const { return m_net_name; }
  119. const wxString& GetGroupName() const { return m_net_name; }
  120. void ResetColumnChangedBits()
  121. {
  122. m_column_changed.reset();
  123. }
  124. #define gen( mvar, chg_bit, get, set, add, sub, changed ) \
  125. decltype( mvar ) get() const \
  126. { \
  127. return mvar; \
  128. } \
  129. \
  130. bool changed() const \
  131. { \
  132. return m_column_changed[chg_bit]; \
  133. } \
  134. \
  135. void set( const decltype( mvar )& aValue ) \
  136. { \
  137. if( m_parent ) \
  138. m_parent->set( m_parent->get() - mvar + aValue ); \
  139. \
  140. static_assert( chg_bit < decltype( m_column_changed )().size(), "" ); \
  141. m_column_changed[chg_bit] = m_column_changed[chg_bit] | ( mvar != aValue ); \
  142. mvar = aValue; \
  143. } \
  144. \
  145. void add( const decltype( mvar )& aValue ) \
  146. { \
  147. if( m_parent ) \
  148. m_parent->add( aValue ); \
  149. \
  150. static_assert( chg_bit < decltype( m_column_changed )().size(), "" ); \
  151. m_column_changed[chg_bit] = m_column_changed[chg_bit] | ( aValue != 0 ); \
  152. mvar += aValue; \
  153. } \
  154. \
  155. void sub( const decltype( mvar )& aValue ) \
  156. { \
  157. if( m_parent ) \
  158. m_parent->sub( aValue ); \
  159. \
  160. static_assert( chg_bit < decltype( m_column_changed )().size(), "" ); \
  161. m_column_changed[chg_bit] = m_column_changed[chg_bit] | ( aValue != 0 ); \
  162. mvar -= aValue; \
  163. }
  164. gen( m_pad_count, 0, GetPadCount, SetPadCount, AddPadCount, SubPadCount, PadCountChanged );
  165. gen( m_via_count, 1, GetViaCount, SetViaCount, AddViaCount, SubViaCount, ViaCountChanged );
  166. gen( m_via_length, 2, GetViaLength, SetViaLength, AddViaLength, SubViaLength, ViaLengthChanged );
  167. gen( m_board_wire_length, 3, GetBoardWireLength, SetBoardWireLength, AddBoardWireLength,
  168. SubBoardWireLength, BoardWireLengthChanged );
  169. gen( m_chip_wire_length, 4, GetChipWireLength, SetChipWireLength, AddChipWireLength,
  170. SubChipWireLength, ChipWireLengthChanged );
  171. #undef gen
  172. // the total length column is always computed, never stored.
  173. unsigned long long int GetTotalLength() const
  174. {
  175. return GetBoardWireLength() + GetViaLength() + GetChipWireLength();
  176. }
  177. bool TotalLengthChanged() const
  178. {
  179. return BoardWireLengthChanged() || ViaLengthChanged() || ChipWireLengthChanged();
  180. }
  181. LIST_ITEM* Parent() const
  182. {
  183. return m_parent;
  184. }
  185. void SetParent( LIST_ITEM* aParent )
  186. {
  187. if( m_parent == aParent )
  188. return;
  189. if( m_parent != nullptr )
  190. {
  191. m_parent->SubPadCount( GetPadCount() );
  192. m_parent->SubViaCount( GetViaCount() );
  193. m_parent->SubViaLength( GetViaLength() );
  194. m_parent->SubBoardWireLength( GetBoardWireLength() );
  195. m_parent->SubChipWireLength( GetChipWireLength() );
  196. m_parent->m_children.erase( std::find( m_parent->m_children.begin(),
  197. m_parent->m_children.end(), this ) );
  198. }
  199. m_parent = aParent;
  200. if( m_parent != nullptr )
  201. {
  202. m_parent->AddPadCount( GetPadCount() );
  203. m_parent->AddViaCount( GetViaCount() );
  204. m_parent->AddViaLength( GetViaLength() );
  205. m_parent->AddBoardWireLength( GetBoardWireLength() );
  206. m_parent->AddChipWireLength( GetChipWireLength() );
  207. m_parent->m_children.push_back( this );
  208. }
  209. }
  210. };
  211. struct DIALOG_NET_INSPECTOR::LIST_ITEM_NETCODE_CMP_LESS
  212. {
  213. template <typename T>
  214. bool operator()( const T& a, const T& b ) const
  215. {
  216. return a->GetNetCode() < b->GetNetCode();
  217. }
  218. template <typename T>
  219. bool operator()( const T& a, int b ) const
  220. {
  221. return a->GetNetCode() < b;
  222. }
  223. template <typename T>
  224. bool operator()( int a, const T& b ) const
  225. {
  226. return a < b->GetNetCode();
  227. }
  228. };
  229. class DIALOG_NET_INSPECTOR::DATA_MODEL : public wxDataViewModel
  230. {
  231. private:
  232. DIALOG_NET_INSPECTOR& m_parent;
  233. // primary container, sorted by netcode number.
  234. // groups have netcode < 0, so they always come first, in the order
  235. // of the filter strings as input by the user (group mode 0, 1) or
  236. // in order of occurrence (group mode 2, 3).
  237. std::vector<std::unique_ptr<LIST_ITEM>> m_items;
  238. public:
  239. static const auto& columnDesc()
  240. {
  241. static const std::array<COLUMN_DESC, 8> r =
  242. { { COLUMN_NET,
  243. COLUMN_NAME,
  244. COLUMN_PAD_COUNT,
  245. COLUMN_VIA_COUNT,
  246. COLUMN_VIA_LENGTH,
  247. COLUMN_BOARD_LENGTH,
  248. COLUMN_CHIP_LENGTH,
  249. COLUMN_TOTAL_LENGTH } };
  250. return r;
  251. }
  252. DATA_MODEL( DIALOG_NET_INSPECTOR& parent ) : m_parent( parent )
  253. {
  254. }
  255. unsigned int columnCount() const
  256. {
  257. return columnDesc().size();
  258. }
  259. unsigned int itemCount() const
  260. {
  261. return m_items.size();
  262. }
  263. wxVariant valueAt( unsigned int aCol, unsigned int aRow ) const
  264. {
  265. wxVariant r;
  266. GetValue( r, wxDataViewItem( const_cast<LIST_ITEM*>( &*( m_items[aRow] ) ) ), aCol );
  267. return r;
  268. }
  269. const LIST_ITEM& itemAt( unsigned int aRow ) const
  270. {
  271. return *m_items.at( aRow );
  272. }
  273. OPT<LIST_ITEM_ITER> findItem( int aNetCode )
  274. {
  275. auto i = std::lower_bound(
  276. m_items.begin(), m_items.end(), aNetCode, LIST_ITEM_NETCODE_CMP_LESS() );
  277. if( i == m_items.end() || ( *i )->GetNetCode() != aNetCode )
  278. return {};
  279. return { i };
  280. }
  281. OPT<LIST_ITEM_ITER> findItem( NETINFO_ITEM* aNet )
  282. {
  283. if( aNet != nullptr )
  284. return findItem( aNet->GetNetCode() );
  285. else
  286. return {};
  287. }
  288. OPT<LIST_ITEM_ITER> addItem( std::unique_ptr<LIST_ITEM> aItem )
  289. {
  290. if( aItem == nullptr )
  291. return {};
  292. // make sure that the vector is always sorted. usually when new nets are added,
  293. // they always get a higher netcode number than the already existing ones.
  294. // however, if we've got filtering enabled, we might not have all the nets in
  295. // our list, so do a sorted insertion.
  296. auto new_iter = std::lower_bound( m_items.begin(), m_items.end(), aItem->GetNetCode(),
  297. LIST_ITEM_NETCODE_CMP_LESS() );
  298. new_iter = m_items.insert( new_iter, std::move( aItem ) );
  299. const std::unique_ptr<LIST_ITEM>& new_item = *new_iter;
  300. if( m_parent.m_groupBy->IsChecked()
  301. && ( m_parent.m_groupByKind->GetSelection() == 0
  302. || m_parent.m_groupByKind->GetSelection() == 1 ) )
  303. {
  304. for( unsigned int j = 0; j < m_parent.m_groupFilter.size(); ++j )
  305. {
  306. if( m_parent.m_groupFilter[j]->Find( new_item->GetNetName() ) )
  307. {
  308. new_item->SetParent( &*m_items[j] );
  309. break;
  310. }
  311. }
  312. }
  313. else if( m_parent.m_groupBy->IsChecked()
  314. && ( m_parent.m_groupByKind->GetSelection() == 2
  315. || m_parent.m_groupByKind->GetSelection() == 3 ) )
  316. {
  317. auto groups_begin = m_items.begin();
  318. auto groups_end = std::find_if_not( m_items.begin(), m_items.end(),
  319. []( const std::unique_ptr<LIST_ITEM>& x )
  320. {
  321. return x->GetIsGroup();
  322. } );
  323. for( std::unique_ptr<EDA_PATTERN_MATCH>& f : m_parent.m_groupFilter )
  324. {
  325. EDA_PATTERN_MATCH::FIND_RESULT match = f->Find( new_item->GetNetName() );
  326. if( match )
  327. {
  328. wxString match_str = new_item->GetNetName().substr( match.start, match.length );
  329. auto group = std::find_if( groups_begin, groups_end,
  330. [&]( const std::unique_ptr<LIST_ITEM>& x )
  331. {
  332. return x->GetNetName() == match_str;
  333. } );
  334. if( group == groups_end )
  335. {
  336. int dist = std::distance( groups_end, groups_begin );
  337. group = m_items.insert( groups_end,
  338. std::make_unique<LIST_ITEM>( dist, match_str ) );
  339. groups_end = group + 1;
  340. ItemAdded( wxDataViewItem(( *group )->Parent() ),
  341. wxDataViewItem( &**group ) );
  342. }
  343. new_item->SetParent( &**group );
  344. break;
  345. }
  346. }
  347. }
  348. ItemAdded( wxDataViewItem( new_item->Parent() ), wxDataViewItem( new_item.get() ) );
  349. return { new_iter };
  350. }
  351. void addItems( std::vector<std::unique_ptr<LIST_ITEM>>&& aItems )
  352. {
  353. if( m_items.empty() )
  354. {
  355. m_items = std::move( aItems );
  356. if( m_parent.m_groupBy->IsChecked()
  357. && ( m_parent.m_groupByKind->GetSelection() == 0
  358. || m_parent.m_groupByKind->GetSelection() == 1 ) )
  359. {
  360. // assume that there are fewer group filters than nets.
  361. // walk over the list items and assign them to groups. note that the
  362. // first items are group items themselves, so start after those.
  363. for( unsigned int i = m_parent.m_groupFilter.size(); i < m_items.size(); ++i )
  364. {
  365. for( unsigned int j = 0; j < m_parent.m_groupFilter.size(); ++j )
  366. {
  367. if( m_parent.m_groupFilter[j]->Find( m_items[ i ]->GetNetName() ) )
  368. {
  369. m_items[i]->SetParent( &*m_items[j] );
  370. break;
  371. }
  372. }
  373. }
  374. }
  375. else if( m_parent.m_groupBy->IsChecked()
  376. && ( m_parent.m_groupByKind->GetSelection() == 2
  377. || m_parent.m_groupByKind->GetSelection() == 3 ) )
  378. {
  379. // assume that there will be fewer resulting groups than nets.
  380. // dynamically generate groups for the actual strings of the match result.
  381. // try out each filter on each item and group by the resulting substrings.
  382. std::vector<std::unique_ptr<LIST_ITEM>> groups;
  383. for( std::unique_ptr<LIST_ITEM>& i : m_items )
  384. {
  385. for( std::unique_ptr<EDA_PATTERN_MATCH>& f : m_parent.m_groupFilter )
  386. {
  387. EDA_PATTERN_MATCH::FIND_RESULT match = f->Find( i->GetNetName() );
  388. if( match )
  389. {
  390. wxString match_str = i->GetNetName().substr( match.start, match.length );
  391. auto group = std::find_if( groups.begin(), groups.end(),
  392. [&]( const std::unique_ptr<LIST_ITEM>& x )
  393. {
  394. return x->GetNetName() == match_str;
  395. } );
  396. if( group == groups.end() )
  397. {
  398. groups.emplace_back( std::make_unique<LIST_ITEM>( groups.size(),
  399. match_str ) );
  400. group = groups.end() - 1;
  401. }
  402. i->SetParent( &**group );
  403. break;
  404. }
  405. }
  406. }
  407. // insert the group items at the front of the items list.
  408. for( std::unique_ptr<LIST_ITEM>& g : groups )
  409. m_items.emplace_back( std::move( g ) );
  410. std::rotate( m_items.begin(), m_items.end() - groups.size(), m_items.end() );
  411. }
  412. for( std::unique_ptr<LIST_ITEM>& i : m_items )
  413. ItemAdded( wxDataViewItem( i->Parent() ), wxDataViewItem( &*i ) );
  414. }
  415. else
  416. {
  417. m_items.reserve( m_items.size() + aItems.size() );
  418. for( std::unique_ptr<LIST_ITEM>& i : aItems )
  419. addItem( std::move( i ) );
  420. }
  421. }
  422. std::unique_ptr<LIST_ITEM> deleteItem( const OPT<LIST_ITEM_ITER>& aRow )
  423. {
  424. if( !aRow )
  425. return {};
  426. std::unique_ptr<LIST_ITEM> i = std::move( **aRow );
  427. // if the row has a parent, detach it first
  428. LIST_ITEM* parent = i->Parent();
  429. i->SetParent( nullptr );
  430. m_items.erase( *aRow );
  431. ItemDeleted( wxDataViewItem( parent ), wxDataViewItem( &*i ) );
  432. if( parent )
  433. {
  434. ItemChanged( wxDataViewItem( parent ) );
  435. // for grouping type 2,3 a group item might disappear if it becomes empty.
  436. if( ( m_parent.m_groupByKind->GetSelection() == 2
  437. || m_parent.m_groupByKind->GetSelection() == 3 )
  438. && parent != nullptr && parent->ChildrenCount() == 0 )
  439. {
  440. auto p = std::find_if( m_items.begin(), m_items.end(),
  441. [&]( std::unique_ptr<LIST_ITEM>& x )
  442. {
  443. return x.get() == parent;
  444. } );
  445. wxASSERT( p != m_items.end() );
  446. m_items.erase( p );
  447. ItemDeleted( wxDataViewItem( parent->Parent() ), wxDataViewItem( parent ) );
  448. }
  449. }
  450. Resort();
  451. return i;
  452. }
  453. void deleteAllItems()
  454. {
  455. BeforeReset();
  456. m_items.clear();
  457. AfterReset();
  458. }
  459. void updateItem( const OPT<LIST_ITEM_ITER>& aRow )
  460. {
  461. if( aRow )
  462. {
  463. const std::unique_ptr<LIST_ITEM>& listItem = *aRow.get();
  464. if( listItem->Parent() )
  465. ItemChanged( wxDataViewItem( listItem->Parent() ) );
  466. ItemChanged( wxDataViewItem( listItem.get() ) );
  467. resortIfChanged( listItem.get() );
  468. }
  469. }
  470. void updateAllItems()
  471. {
  472. for( std::unique_ptr<LIST_ITEM>& i : m_items )
  473. ItemChanged( wxDataViewItem( i.get() ) );
  474. }
  475. void resortIfChanged( LIST_ITEM* aItem )
  476. {
  477. if( wxDataViewColumn* column = m_parent.m_netsList->GetSortingColumn() )
  478. {
  479. bool changed = false;
  480. for( const LIST_ITEM* i = aItem; i != nullptr; i = i->Parent() )
  481. changed |= itemColumnChanged( i, column->GetModelColumn() );
  482. for( LIST_ITEM* i = aItem; i != nullptr; i = i->Parent() )
  483. i->ResetColumnChangedBits();
  484. if( changed )
  485. Resort();
  486. }
  487. }
  488. bool itemColumnChanged( const LIST_ITEM* aItem, unsigned int aCol ) const
  489. {
  490. if( aItem == nullptr || aCol >= columnDesc().size() )
  491. return false;
  492. if( aCol == COLUMN_PAD_COUNT )
  493. return aItem->PadCountChanged();
  494. else if( aCol == COLUMN_VIA_COUNT )
  495. return aItem->ViaCountChanged();
  496. else if( aCol == COLUMN_VIA_LENGTH )
  497. return aItem->ViaLengthChanged();
  498. else if( aCol == COLUMN_BOARD_LENGTH )
  499. return aItem->BoardWireLengthChanged();
  500. else if( aCol == COLUMN_CHIP_LENGTH )
  501. return aItem->ChipWireLengthChanged();
  502. else if( aCol == COLUMN_TOTAL_LENGTH )
  503. return aItem->TotalLengthChanged();
  504. return false;
  505. }
  506. // implementation of wxDataViewModel interface
  507. // these are used to query the data model by the GUI view implementation.
  508. // these are not supposed to be used to modify the data model. for that
  509. // use the public functions above.
  510. protected:
  511. unsigned int GetColumnCount() const override
  512. {
  513. return columnCount();
  514. }
  515. void GetValue( wxVariant& aOutValue, const wxDataViewItem& aItem,
  516. unsigned int aCol ) const override
  517. {
  518. if( LIST_ITEM* i = static_cast<LIST_ITEM*>( aItem.GetID() ) )
  519. {
  520. if( aCol == COLUMN_NET && !i->GetIsGroup() )
  521. aOutValue = m_parent.formatNetCode( i->GetNet() );
  522. else if( aCol == COLUMN_NET && i->GetIsGroup() )
  523. aOutValue = "";
  524. else if( aCol == COLUMN_NAME )
  525. aOutValue = i->GetNetName();
  526. else if( aCol == COLUMN_PAD_COUNT )
  527. aOutValue = m_parent.formatCount( i->GetPadCount() );
  528. else if( aCol == COLUMN_VIA_COUNT )
  529. aOutValue = m_parent.formatCount( i->GetViaCount() );
  530. else if( aCol == COLUMN_VIA_LENGTH )
  531. aOutValue = m_parent.formatLength( i->GetViaLength() );
  532. else if( aCol == COLUMN_BOARD_LENGTH )
  533. aOutValue = m_parent.formatLength( i->GetBoardWireLength() );
  534. else if( aCol == COLUMN_CHIP_LENGTH )
  535. aOutValue = m_parent.formatLength( i->GetChipWireLength() );
  536. else if( aCol == COLUMN_TOTAL_LENGTH )
  537. aOutValue = m_parent.formatLength( i->GetTotalLength() );
  538. }
  539. }
  540. static int compareUInt( uint64_t aValue1, uint64_t aValue2, bool aAsc )
  541. {
  542. if( aAsc )
  543. return aValue1 < aValue2 ? -1 : 1;
  544. else
  545. return aValue2 < aValue1 ? -1 : 1;
  546. }
  547. int Compare( const wxDataViewItem& aItem1, const wxDataViewItem& aItem2,
  548. unsigned int aCol, bool aAsc ) const override
  549. {
  550. const LIST_ITEM& i1 = *static_cast<const LIST_ITEM*>( aItem1.GetID() );
  551. const LIST_ITEM& i2 = *static_cast<const LIST_ITEM*>( aItem2.GetID() );
  552. if( i1.GetIsGroup() && !i2.GetIsGroup() )
  553. return -1;
  554. if( i2.GetIsGroup() && !i1.GetIsGroup() )
  555. return 1;
  556. if( aCol == COLUMN_NET && i1.GetNetCode() != i2.GetNetCode() )
  557. {
  558. return aAsc ? ( i2.GetNetCode() - i1.GetNetCode() ) : ( i1.GetNetCode() - i2.GetNetCode() );
  559. }
  560. else if( aCol == COLUMN_NAME )
  561. {
  562. const wxString& s1 = i1.GetNetName();
  563. const wxString& s2 = i2.GetNetName();
  564. int res = aAsc ? s1.Cmp( s2 ) : s2.Cmp( s1 );
  565. if( res != 0 )
  566. return res;
  567. }
  568. else if( aCol == COLUMN_PAD_COUNT && i1.GetPadCount() != i2.GetPadCount() )
  569. return compareUInt( i1.GetPadCount(), i2.GetPadCount(), aAsc );
  570. else if( aCol == COLUMN_VIA_COUNT && i1.GetViaCount() != i2.GetViaCount() )
  571. return compareUInt( i1.GetViaCount(), i2.GetViaCount(), aAsc );
  572. else if( aCol == COLUMN_VIA_LENGTH && i1.GetViaLength() != i2.GetViaLength() )
  573. return compareUInt( i1.GetViaLength(), i2.GetViaLength(), aAsc );
  574. else if( aCol == COLUMN_BOARD_LENGTH && i1.GetBoardWireLength() != i2.GetBoardWireLength() )
  575. return compareUInt( i1.GetBoardWireLength(), i2.GetBoardWireLength(), aAsc );
  576. else if( aCol == COLUMN_CHIP_LENGTH && i1.GetChipWireLength() != i2.GetChipWireLength() )
  577. return compareUInt( i1.GetChipWireLength(), i2.GetChipWireLength(), aAsc );
  578. else if( aCol == COLUMN_TOTAL_LENGTH && i1.GetTotalLength() != i2.GetTotalLength() )
  579. return compareUInt( i1.GetTotalLength(), i2.GetTotalLength(), aAsc );
  580. // when the item values compare equal resort to pointer comparison.
  581. wxUIntPtr id1 = wxPtrToUInt( aItem1.GetID() );
  582. wxUIntPtr id2 = wxPtrToUInt( aItem2.GetID() );
  583. return aAsc ? id1 - id2 : id2 - id1;
  584. }
  585. bool SetValue( const wxVariant& aInValue, const wxDataViewItem& aItem,
  586. unsigned int aCol ) override
  587. {
  588. return false;
  589. }
  590. wxDataViewItem GetParent( const wxDataViewItem& aItem ) const override
  591. {
  592. if( !aItem.IsOk() )
  593. return wxDataViewItem();
  594. return wxDataViewItem( static_cast<const LIST_ITEM*>( aItem.GetID() )->Parent() );
  595. }
  596. bool IsContainer( const wxDataViewItem& aItem ) const override
  597. {
  598. if( !aItem.IsOk() )
  599. return true;
  600. return static_cast<const LIST_ITEM*>( aItem.GetID() )->GetIsGroup();
  601. }
  602. bool HasContainerColumns( const wxDataViewItem& aItem ) const override
  603. {
  604. return IsContainer( aItem );
  605. }
  606. unsigned int GetChildren( const wxDataViewItem& aParent,
  607. wxDataViewItemArray& aChildren ) const override
  608. {
  609. const LIST_ITEM* p = static_cast<const LIST_ITEM*>( aParent.GetID() );
  610. if( !aParent.IsOk() )
  611. {
  612. aChildren.Alloc( m_items.size() );
  613. for( const std::unique_ptr<LIST_ITEM>& i : m_items )
  614. {
  615. if( i->Parent() == nullptr )
  616. aChildren.Add( wxDataViewItem( &*i ) );
  617. }
  618. return aChildren.GetCount();
  619. }
  620. else if( p->GetIsGroup() )
  621. {
  622. const int count = p->ChildrenCount();
  623. if( count == 0 )
  624. return 0;
  625. aChildren.Alloc( count );
  626. for( auto i = p->ChildrenBegin(), end = p->ChildrenEnd(); i != end; ++i )
  627. aChildren.Add( wxDataViewItem( *i ) );
  628. return aChildren.GetCount();
  629. }
  630. return 0;
  631. }
  632. wxString GetColumnType( unsigned int /* aCol */ ) const override
  633. {
  634. return wxS( "string" );
  635. }
  636. };
  637. DIALOG_NET_INSPECTOR::DIALOG_NET_INSPECTOR( PCB_EDIT_FRAME* aParent,
  638. const SETTINGS& aSettings ) :
  639. DIALOG_NET_INSPECTOR_BASE( aParent ),
  640. m_zero_netitem( nullptr ),
  641. m_frame( aParent )
  642. {
  643. m_brd = aParent->GetBoard();
  644. m_data_model = new DATA_MODEL( *this );
  645. m_netsList->AssociateModel( &*m_data_model );
  646. std::array<std::function<void( void )>, 8> add_col = {
  647. [&]()
  648. {
  649. m_netsList->AppendTextColumn( COLUMN_NET.display_name, COLUMN_NET,
  650. wxDATAVIEW_CELL_INERT, -1, wxALIGN_LEFT,
  651. wxDATAVIEW_COL_SORTABLE );
  652. },
  653. [&]()
  654. {
  655. m_netsList->AppendTextColumn( COLUMN_NAME.display_name, COLUMN_NAME,
  656. wxDATAVIEW_CELL_INERT, -1, wxALIGN_LEFT,
  657. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE |
  658. wxDATAVIEW_COL_SORTABLE );
  659. },
  660. [&]()
  661. {
  662. m_netsList->AppendTextColumn( COLUMN_PAD_COUNT.display_name, COLUMN_PAD_COUNT,
  663. wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER,
  664. wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE );
  665. },
  666. [&]()
  667. {
  668. m_netsList->AppendTextColumn( COLUMN_VIA_COUNT.display_name, COLUMN_VIA_COUNT,
  669. wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER,
  670. wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE );
  671. },
  672. [&]()
  673. {
  674. m_netsList->AppendTextColumn( COLUMN_VIA_LENGTH.display_name, COLUMN_VIA_LENGTH,
  675. wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER,
  676. wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE );
  677. },
  678. [&]()
  679. {
  680. m_netsList->AppendTextColumn( COLUMN_BOARD_LENGTH.display_name, COLUMN_BOARD_LENGTH,
  681. wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER,
  682. wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE );
  683. },
  684. [&]()
  685. {
  686. m_netsList->AppendTextColumn( COLUMN_CHIP_LENGTH.display_name, COLUMN_CHIP_LENGTH,
  687. wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER,
  688. wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE );
  689. },
  690. [&]()
  691. {
  692. m_netsList->AppendTextColumn( COLUMN_TOTAL_LENGTH.display_name, COLUMN_TOTAL_LENGTH,
  693. wxDATAVIEW_CELL_INERT, -1, wxALIGN_CENTER,
  694. wxDATAVIEW_COL_REORDERABLE | wxDATAVIEW_COL_SORTABLE );
  695. }
  696. };
  697. std::vector<int> col_order = aSettings.column_order;
  698. if( col_order.size() != add_col.size() )
  699. {
  700. col_order.resize( add_col.size() );
  701. for( unsigned int i = 0; i < add_col.size(); ++i )
  702. col_order[i] = i;
  703. }
  704. for( unsigned int i : col_order )
  705. add_col.at( i )();
  706. m_netsList->SetExpanderColumn( m_netsList->GetColumn( 0 ) );
  707. // avoid onFilterChange for each of the settings as it will re-build the
  708. // list over and over again.
  709. m_filter_change_no_rebuild = true;
  710. m_textCtrlFilter->SetValue( aSettings.filter_string );
  711. m_cbShowZeroPad->SetValue( aSettings.show_zero_pad_nets );
  712. m_groupBy->SetValue( aSettings.group_by );
  713. m_groupByKind->SetSelection( aSettings.group_by_kind );
  714. m_groupByText->SetValue( aSettings.group_by_text );
  715. m_filter_change_no_rebuild = false;
  716. buildNetsList();
  717. adjustListColumns();
  718. m_addNet->SetBitmap( KiBitmap( BITMAPS::small_plus ) );
  719. m_renameNet->SetBitmap( KiBitmap( BITMAPS::small_edit ) );
  720. m_deleteNet->SetBitmap( KiBitmap( BITMAPS::small_trash ) );
  721. m_sdbSizerOK->SetDefault();
  722. m_renameNet->Disable();
  723. m_deleteNet->Disable();
  724. if( aSettings.sorting_column != -1 )
  725. {
  726. if( wxDataViewColumn* c = m_netsList->GetColumn( aSettings.sorting_column ) )
  727. {
  728. c->SetSortOrder( aSettings.sort_order_asc );
  729. m_data_model->Resort();
  730. }
  731. }
  732. finishDialogSettings();
  733. m_frame->Connect( wxEVT_CLOSE_WINDOW,
  734. wxCommandEventHandler( DIALOG_NET_INSPECTOR::onParentWindowClosed ),
  735. nullptr, this );
  736. m_frame->Connect( UNITS_CHANGED,
  737. wxCommandEventHandler( DIALOG_NET_INSPECTOR::onUnitsChanged ),
  738. nullptr, this );
  739. m_frame->Connect( BOARD_CHANGED,
  740. wxCommandEventHandler( DIALOG_NET_INSPECTOR::onBoardChanged ),
  741. nullptr, this );
  742. if( m_brd != nullptr )
  743. {
  744. // if the dialog is opened while something is highlighted on the board ...
  745. OnBoardHighlightNetChanged( *m_brd );
  746. m_brd->AddListener( this );
  747. }
  748. }
  749. DIALOG_NET_INSPECTOR::~DIALOG_NET_INSPECTOR()
  750. {
  751. // the displayed list elements are going to be deleted before the list view itself.
  752. // in some cases it might still do queries on the data model, which would crash
  753. // from now on. so just disassociate it.
  754. m_netsList->AssociateModel( nullptr );
  755. m_frame->Disconnect( wxEVT_CLOSE_WINDOW,
  756. wxCommandEventHandler( DIALOG_NET_INSPECTOR::onParentWindowClosed ),
  757. nullptr, this );
  758. m_frame->Disconnect( UNITS_CHANGED,
  759. wxCommandEventHandler( DIALOG_NET_INSPECTOR::onUnitsChanged ),
  760. nullptr, this );
  761. m_frame->Disconnect( BOARD_CHANGED,
  762. wxCommandEventHandler( DIALOG_NET_INSPECTOR::onBoardChanged ),
  763. nullptr, this );
  764. if( m_brd != nullptr )
  765. m_brd->RemoveListener( this );
  766. m_frame->GetCanvas()->SetFocus();
  767. }
  768. DIALOG_NET_INSPECTOR::SETTINGS DIALOG_NET_INSPECTOR::Settings() const
  769. {
  770. std::vector<int> column_order( m_data_model->columnCount() );
  771. for( unsigned int i = 0; i < column_order.size(); ++i )
  772. column_order[i] = m_netsList->GetColumn( i )->GetModelColumn();
  773. wxDataViewColumn* sorting_column = m_netsList->GetSortingColumn();
  774. SETTINGS r;
  775. r.filter_string = m_textCtrlFilter->GetValue();
  776. r.show_zero_pad_nets = m_cbShowZeroPad->IsChecked();
  777. r.group_by = m_groupBy->IsChecked();
  778. r.group_by_kind = m_groupByKind->GetSelection();
  779. r.group_by_text = m_groupByText->GetValue();
  780. r.sorting_column = sorting_column ? static_cast<int>( sorting_column->GetModelColumn() ) : -1;
  781. r.sort_order_asc = sorting_column ? sorting_column->IsSortOrderAscending() : true;
  782. r.column_order = column_order;
  783. return r;
  784. }
  785. void DIALOG_NET_INSPECTOR::onParentWindowClosed( wxCommandEvent& event )
  786. {
  787. Close();
  788. event.Skip();
  789. }
  790. void DIALOG_NET_INSPECTOR::onUnitsChanged( wxCommandEvent& event )
  791. {
  792. this->m_units = m_frame->GetUserUnits();
  793. m_data_model->updateAllItems();
  794. event.Skip();
  795. }
  796. void DIALOG_NET_INSPECTOR::onBoardChanged( wxCommandEvent& event )
  797. {
  798. m_brd = m_frame->GetBoard();
  799. if( m_brd != nullptr )
  800. m_brd->AddListener( this );
  801. buildNetsList();
  802. m_netsList->Refresh();
  803. event.Skip();
  804. }
  805. bool DIALOG_NET_INSPECTOR::netFilterMatches( NETINFO_ITEM* aNet ) const
  806. {
  807. // Note: the filtering is case insensitive.
  808. // Never show the unconnected net
  809. if( aNet->GetNetCode() <= 0 )
  810. return false;
  811. // Show unconnected nets only if specifically asked for by filter
  812. if( m_netFilter.empty() )
  813. return !aNet->GetNetname().StartsWith( "unconnected-(" );
  814. wxString net_str = UnescapeString( aNet->GetNetname() ).Upper();
  815. for( const std::unique_ptr<EDA_PATTERN_MATCH>& f : m_netFilter )
  816. {
  817. if( f->Find( net_str ) )
  818. return true;
  819. }
  820. return false;
  821. }
  822. struct NETCODE_CMP_LESS
  823. {
  824. bool operator()( const CN_ITEM* a, const CN_ITEM* b ) const
  825. {
  826. return a->Net() < b->Net();
  827. }
  828. bool operator()( const CN_ITEM* a, int b ) const
  829. {
  830. return a->Net() < b;
  831. }
  832. bool operator()( int a, const CN_ITEM* b ) const
  833. {
  834. return a < b->Net();
  835. }
  836. };
  837. std::vector<CN_ITEM*> DIALOG_NET_INSPECTOR::relevantConnectivityItems() const
  838. {
  839. // pre-filter the connectivity items and sort them by netcode.
  840. // this avoids quadratic runtime when building the whole net list and
  841. // calculating the total length for each net.
  842. const auto type_bits = std::bitset<MAX_STRUCT_TYPE_ID>()
  843. .set( PCB_TRACE_T )
  844. .set( PCB_ARC_T )
  845. .set( PCB_VIA_T )
  846. .set( PCB_PAD_T );
  847. std::vector<CN_ITEM*> cn_items;
  848. cn_items.reserve( 1024 );
  849. for( CN_ITEM* cn_item : m_brd->GetConnectivity()->GetConnectivityAlgo()->ItemList() )
  850. {
  851. if( cn_item->Valid() && type_bits[cn_item->Parent()->Type()] )
  852. cn_items.push_back( cn_item );
  853. }
  854. std::sort( cn_items.begin(), cn_items.end(), NETCODE_CMP_LESS() );
  855. return cn_items;
  856. }
  857. void DIALOG_NET_INSPECTOR::updateDisplayedRowValues( const OPT<LIST_ITEM_ITER>& aRow )
  858. {
  859. if( !aRow )
  860. return;
  861. wxDataViewItemArray sel;
  862. m_netsList->GetSelections( sel );
  863. m_data_model->updateItem( aRow );
  864. if( !sel.IsEmpty() )
  865. {
  866. m_netsList->SetSelections( sel );
  867. m_netsList->EnsureVisible( sel.Item( 0 ) );
  868. }
  869. }
  870. wxString DIALOG_NET_INSPECTOR::formatNetCode( const NETINFO_ITEM* aNet ) const
  871. {
  872. return wxString::Format( "%.3d", aNet->GetNetCode() );
  873. }
  874. wxString DIALOG_NET_INSPECTOR::formatNetName( const NETINFO_ITEM* aNet ) const
  875. {
  876. return UnescapeString( aNet->GetNetname() );
  877. }
  878. wxString DIALOG_NET_INSPECTOR::formatCount( unsigned int aValue ) const
  879. {
  880. return wxString::Format( "%u", aValue );
  881. }
  882. wxString DIALOG_NET_INSPECTOR::formatLength( int64_t aValue ) const
  883. {
  884. return MessageTextFromValue( GetUserUnits(), static_cast<long long int>( aValue ) );
  885. }
  886. void DIALOG_NET_INSPECTOR::OnBoardItemAdded( BOARD& aBoard, BOARD_ITEM* aBoardItem )
  887. {
  888. if( NETINFO_ITEM* net = dynamic_cast<NETINFO_ITEM*>( aBoardItem ) )
  889. {
  890. // a new net has been added to the board. add it to our list if it
  891. // passes the netname filter test.
  892. if( netFilterMatches( net ) )
  893. {
  894. std::unique_ptr<LIST_ITEM> new_item = std::make_unique<LIST_ITEM>( net );
  895. // the new net could have some pads already assigned, count them.
  896. new_item->SetPadCount( m_brd->GetNodesCount( net->GetNetCode() ) );
  897. m_data_model->addItem( std::move( new_item ) );
  898. }
  899. }
  900. else if( BOARD_CONNECTED_ITEM* i = dynamic_cast<BOARD_CONNECTED_ITEM*>( aBoardItem ) )
  901. {
  902. OPT<LIST_ITEM_ITER> r = m_data_model->findItem( i->GetNet() );
  903. if( r )
  904. {
  905. // try to handle frequent operations quickly.
  906. if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( i ) )
  907. {
  908. const std::unique_ptr<LIST_ITEM>& list_item = *r.get();
  909. int len = track->GetLength();
  910. list_item->AddBoardWireLength( len );
  911. if( track->Type() == PCB_VIA_T )
  912. {
  913. list_item->AddViaCount( 1 );
  914. list_item->AddViaLength( calculateViaLength( track ) );
  915. }
  916. updateDisplayedRowValues( r );
  917. return;
  918. }
  919. }
  920. // resort to generic slower net update otherwise.
  921. updateNet( i->GetNet() );
  922. }
  923. else if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( aBoardItem ) )
  924. {
  925. for( const PAD* pad : footprint->Pads() )
  926. {
  927. OPT<LIST_ITEM_ITER> r = m_data_model->findItem( pad->GetNet() );
  928. if( !r )
  929. {
  930. // if show-zero-pads is off, we might not have this net
  931. // in our list yet, so add it first.
  932. // notice that at this point we are very certain that this net
  933. // will have at least one pad.
  934. if( netFilterMatches( pad->GetNet() ) )
  935. r = m_data_model->addItem( std::make_unique<LIST_ITEM>( pad->GetNet() ) );
  936. }
  937. if( r )
  938. {
  939. const std::unique_ptr<LIST_ITEM>& list_item = *r.get();
  940. int len = pad->GetPadToDieLength();
  941. list_item->AddPadCount( 1 );
  942. list_item->AddChipWireLength( len );
  943. if( list_item->GetPadCount() == 0 && !m_cbShowZeroPad->IsChecked() )
  944. m_data_model->deleteItem( r );
  945. else
  946. updateDisplayedRowValues( r );
  947. }
  948. }
  949. }
  950. }
  951. void DIALOG_NET_INSPECTOR::OnBoardItemsAdded( BOARD& aBoard, std::vector<BOARD_ITEM*>& aBoardItem )
  952. {
  953. for( BOARD_ITEM* item : aBoardItem )
  954. {
  955. OnBoardItemAdded( aBoard, item );
  956. }
  957. }
  958. void DIALOG_NET_INSPECTOR::OnBoardItemRemoved( BOARD& aBoard, BOARD_ITEM* aBoardItem )
  959. {
  960. if( NETINFO_ITEM* net = dynamic_cast<NETINFO_ITEM*>( aBoardItem ) )
  961. {
  962. m_data_model->deleteItem( m_data_model->findItem( net ) );
  963. }
  964. else if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( aBoardItem ) )
  965. {
  966. for( const PAD* pad : footprint->Pads() )
  967. {
  968. OPT<LIST_ITEM_ITER> r = m_data_model->findItem( pad->GetNet() );
  969. if( r )
  970. {
  971. const std::unique_ptr<LIST_ITEM>& list_item = *r.get();
  972. int len = pad->GetPadToDieLength();
  973. list_item->SubPadCount( 1 );
  974. list_item->SubChipWireLength( len );
  975. if( list_item->GetPadCount() == 0 && !m_cbShowZeroPad->IsChecked() )
  976. m_data_model->deleteItem( r );
  977. else
  978. updateDisplayedRowValues( r );
  979. }
  980. }
  981. }
  982. else if( BOARD_CONNECTED_ITEM* i = dynamic_cast<BOARD_CONNECTED_ITEM*>( aBoardItem ) )
  983. {
  984. OPT<LIST_ITEM_ITER> r = m_data_model->findItem( i->GetNet() );
  985. if( r )
  986. {
  987. // try to handle frequent operations quickly.
  988. if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( i ) )
  989. {
  990. const std::unique_ptr<LIST_ITEM>& list_item = *r.get();
  991. int len = track->GetLength();
  992. list_item->SubBoardWireLength( len );
  993. if( track->Type() == PCB_VIA_T )
  994. {
  995. list_item->SubViaCount( 1 );
  996. list_item->SubViaLength( calculateViaLength( track ) );
  997. }
  998. updateDisplayedRowValues( r );
  999. return;
  1000. }
  1001. // resort to generic slower net update otherwise.
  1002. updateNet( i->GetNet() );
  1003. }
  1004. }
  1005. }
  1006. void DIALOG_NET_INSPECTOR::OnBoardItemsRemoved( BOARD& aBoard,
  1007. std::vector<BOARD_ITEM*>& aBoardItems )
  1008. {
  1009. for( BOARD_ITEM* item : aBoardItems )
  1010. {
  1011. OnBoardItemRemoved( aBoard, item );
  1012. }
  1013. }
  1014. void DIALOG_NET_INSPECTOR::OnBoardItemChanged( BOARD& aBoard, BOARD_ITEM* aBoardItem )
  1015. {
  1016. if( dynamic_cast<BOARD_CONNECTED_ITEM*>( aBoardItem ) != nullptr
  1017. || dynamic_cast<FOOTPRINT*>( aBoardItem ) != nullptr )
  1018. {
  1019. buildNetsList();
  1020. m_netsList->Refresh();
  1021. }
  1022. }
  1023. void DIALOG_NET_INSPECTOR::OnBoardItemsChanged( BOARD& aBoard,
  1024. std::vector<BOARD_ITEM*>& aBoardItems )
  1025. {
  1026. buildNetsList();
  1027. m_netsList->Refresh();
  1028. }
  1029. void DIALOG_NET_INSPECTOR::OnBoardHighlightNetChanged( BOARD& aBoard )
  1030. {
  1031. if( !m_brd->IsHighLightNetON() )
  1032. {
  1033. m_netsList->UnselectAll();
  1034. }
  1035. else
  1036. {
  1037. const std::set<int>& selected_codes = m_brd->GetHighLightNetCodes();
  1038. wxDataViewItemArray new_selection;
  1039. new_selection.Alloc( selected_codes.size() );
  1040. for( int code : selected_codes )
  1041. {
  1042. if( OPT<LIST_ITEM_ITER> r = m_data_model->findItem( code ) )
  1043. new_selection.Add( wxDataViewItem( &***r ) );
  1044. }
  1045. m_netsList->SetSelections( new_selection );
  1046. if( !new_selection.IsEmpty() )
  1047. m_netsList->EnsureVisible( new_selection.Item( 0 ) );
  1048. }
  1049. }
  1050. void DIALOG_NET_INSPECTOR::OnBoardNetSettingsChanged( BOARD& aBoard )
  1051. {
  1052. buildNetsList();
  1053. m_netsList->Refresh();
  1054. }
  1055. void DIALOG_NET_INSPECTOR::updateNet( NETINFO_ITEM* aNet )
  1056. {
  1057. // something for the specified net has changed, update that row.
  1058. // ignore nets that are not in our list because the filter doesn't match.
  1059. if( !netFilterMatches( aNet ) )
  1060. {
  1061. m_data_model->deleteItem( m_data_model->findItem( aNet ) );
  1062. return;
  1063. }
  1064. // if the net had no pads before, it might not be in the displayed list yet.
  1065. // if it had pads and now doesn't anymore, we might need to remove it from the list.
  1066. OPT<LIST_ITEM_ITER> cur_net_row = m_data_model->findItem( aNet );
  1067. const unsigned int node_count = m_brd->GetNodesCount( aNet->GetNetCode() );
  1068. if( node_count == 0 && !m_cbShowZeroPad->IsChecked() )
  1069. {
  1070. m_data_model->deleteItem( cur_net_row );
  1071. return;
  1072. }
  1073. std::unique_ptr<LIST_ITEM> new_list_item = buildNewItem( aNet, node_count,
  1074. relevantConnectivityItems() );
  1075. if( !cur_net_row )
  1076. {
  1077. m_data_model->addItem( std::move( new_list_item ) );
  1078. return;
  1079. }
  1080. const std::unique_ptr<LIST_ITEM>& cur_list_item = *cur_net_row.get();
  1081. if( cur_list_item->GetNetName() != new_list_item->GetNetName() )
  1082. {
  1083. // if the name has changed, it might require re-grouping.
  1084. // it's easier to remove and re-insert it
  1085. m_data_model->deleteItem( cur_net_row );
  1086. m_data_model->addItem( std::move( new_list_item ) );
  1087. }
  1088. else
  1089. {
  1090. // update fields only
  1091. cur_list_item->SetPadCount( new_list_item->GetPadCount() );
  1092. cur_list_item->SetViaCount( new_list_item->GetViaCount() );
  1093. cur_list_item->SetBoardWireLength( new_list_item->GetBoardWireLength() );
  1094. cur_list_item->SetChipWireLength( new_list_item->GetChipWireLength() );
  1095. updateDisplayedRowValues( cur_net_row );
  1096. }
  1097. }
  1098. unsigned int DIALOG_NET_INSPECTOR::calculateViaLength( const PCB_TRACK* aTrack ) const
  1099. {
  1100. const PCB_VIA& via = dynamic_cast<const PCB_VIA&>( *aTrack );
  1101. BOARD_DESIGN_SETTINGS& bds = m_brd->GetDesignSettings();
  1102. // calculate the via length individually from the board stackup and via's start and end layer.
  1103. if( bds.m_HasStackup )
  1104. {
  1105. const BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
  1106. return stackup.GetLayerDistance( via.TopLayer(), via.BottomLayer() );
  1107. }
  1108. else
  1109. {
  1110. int dielectricLayers = bds.GetCopperLayerCount() - 1;
  1111. int layerThickness = bds.GetBoardThickness() / dielectricLayers;
  1112. int effectiveBottomLayer;
  1113. if( via.BottomLayer() == B_Cu )
  1114. effectiveBottomLayer = F_Cu + dielectricLayers;
  1115. else
  1116. effectiveBottomLayer = via.BottomLayer();
  1117. int layerCount = effectiveBottomLayer - via.TopLayer();
  1118. return layerCount * layerThickness;
  1119. }
  1120. }
  1121. std::unique_ptr<DIALOG_NET_INSPECTOR::LIST_ITEM>
  1122. DIALOG_NET_INSPECTOR::buildNewItem( NETINFO_ITEM* aNet, unsigned int aPadCount,
  1123. const std::vector<CN_ITEM*>& aCNItems )
  1124. {
  1125. std::unique_ptr<LIST_ITEM> new_item = std::make_unique<LIST_ITEM>( aNet );
  1126. new_item->SetPadCount( aPadCount );
  1127. const auto cn_items = std::equal_range( aCNItems.begin(), aCNItems.end(), aNet->GetNetCode(),
  1128. NETCODE_CMP_LESS() );
  1129. for( auto i = cn_items.first; i != cn_items.second; ++i )
  1130. {
  1131. BOARD_CONNECTED_ITEM* item = ( *i )->Parent();
  1132. if( item->Type() == PCB_PAD_T )
  1133. new_item->AddChipWireLength( static_cast<PAD*>( item )->GetPadToDieLength() );
  1134. else if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
  1135. {
  1136. new_item->AddBoardWireLength( track->GetLength() );
  1137. if( item->Type() == PCB_VIA_T )
  1138. {
  1139. new_item->AddViaCount( 1 );
  1140. new_item->AddViaLength( calculateViaLength( track ) );
  1141. }
  1142. }
  1143. }
  1144. return new_item;
  1145. }
  1146. void DIALOG_NET_INSPECTOR::buildNetsList()
  1147. {
  1148. // Only build the list of nets if there is a board present
  1149. if( !m_brd )
  1150. return;
  1151. m_in_build_nets_list = true;
  1152. // when rebuilding the netlist, try to keep the row selection
  1153. // FIXME: handle group selections, preserve expanded/collapsed group states
  1154. wxDataViewItemArray sel;
  1155. m_netsList->GetSelections( sel );
  1156. std::vector<int> prev_selected_netcodes;
  1157. prev_selected_netcodes.reserve( sel.GetCount() );
  1158. for( unsigned int i = 0; i < sel.GetCount(); ++i )
  1159. {
  1160. const LIST_ITEM* item = static_cast<const LIST_ITEM*>( sel.Item( i ).GetID() );
  1161. prev_selected_netcodes.push_back( item->GetNetCode() );
  1162. }
  1163. m_data_model->deleteAllItems();
  1164. std::vector<std::unique_ptr<LIST_ITEM>> new_items;
  1165. // for group mode 0,1 each group filter string represents one displayed group,
  1166. // so just add them first. for group mode 2,3 the groups are generated dynamically.
  1167. if( m_groupBy->IsChecked()
  1168. && ( m_groupByKind->GetSelection() == 0 || m_groupByKind->GetSelection() == 1 ) )
  1169. {
  1170. for( unsigned int i = 0; i < m_groupFilter.size(); ++i )
  1171. {
  1172. const std::unique_ptr<EDA_PATTERN_MATCH>& filter = m_groupFilter[i];
  1173. new_items.emplace_back( std::make_unique<LIST_ITEM>( i, filter->GetPattern() ) );
  1174. }
  1175. }
  1176. std::vector<CN_ITEM*> prefiltered_cn_items = relevantConnectivityItems();
  1177. // collect all nets which pass the filter string and also remember the
  1178. // suffix after the filter match, if any.
  1179. struct NET_INFO
  1180. {
  1181. int netcode;
  1182. NETINFO_ITEM* net;
  1183. unsigned int pad_count;
  1184. };
  1185. struct NET_INFO_CMP_LESS
  1186. {
  1187. bool operator()( const NET_INFO& a, const NET_INFO& b ) const
  1188. {
  1189. return a.netcode < b.netcode;
  1190. }
  1191. bool operator()( const NET_INFO& a, int b ) const
  1192. {
  1193. return a.netcode < b;
  1194. }
  1195. bool operator()( int a, const NET_INFO& b ) const
  1196. {
  1197. return a < b.netcode;
  1198. }
  1199. };
  1200. std::vector<NET_INFO> nets;
  1201. nets.reserve( m_brd->GetNetInfo().NetsByNetcode().size() );
  1202. for( const std::pair<int, NETINFO_ITEM*> ni : m_brd->GetNetInfo().NetsByNetcode() )
  1203. {
  1204. if( ni.first == 0 )
  1205. m_zero_netitem = ni.second;
  1206. if( netFilterMatches( ni.second ) )
  1207. nets.emplace_back( NET_INFO{ ni.first, ni.second, 0 } );
  1208. }
  1209. // count the pads for each net. since the nets are sorted by netcode
  1210. // iterating over the footprints' pads is faster.
  1211. for( FOOTPRINT* footprint : m_brd->Footprints() )
  1212. {
  1213. for( PAD* pad : footprint->Pads() )
  1214. {
  1215. auto i = std::lower_bound( nets.begin(), nets.end(), pad->GetNetCode(),
  1216. NET_INFO_CMP_LESS() );
  1217. if( i != nets.end() && i->netcode == pad->GetNetCode() )
  1218. i->pad_count += 1;
  1219. }
  1220. }
  1221. for( NET_INFO& ni : nets )
  1222. {
  1223. if( m_cbShowZeroPad->IsChecked() || ni.pad_count > 0 )
  1224. new_items.emplace_back( buildNewItem( ni.net, ni.pad_count, prefiltered_cn_items ) );
  1225. }
  1226. m_data_model->addItems( std::move( new_items ) );
  1227. // try to restore the selected rows. set the ones that we can't find anymore to -1.
  1228. sel.Clear();
  1229. for( int& nc : prev_selected_netcodes )
  1230. {
  1231. auto r = m_data_model->findItem( nc );
  1232. if( r )
  1233. {
  1234. const std::unique_ptr<LIST_ITEM>& list_item = *r.get();
  1235. sel.Add( wxDataViewItem( list_item.get() ) );
  1236. }
  1237. else
  1238. {
  1239. nc = -1;
  1240. }
  1241. }
  1242. if( !sel.IsEmpty() )
  1243. {
  1244. m_netsList->SetSelections( sel );
  1245. m_netsList->EnsureVisible( sel.Item( 0 ) );
  1246. }
  1247. else
  1248. {
  1249. m_netsList->UnselectAll();
  1250. }
  1251. prev_selected_netcodes.erase( std::remove( prev_selected_netcodes.begin(),
  1252. prev_selected_netcodes.end(), -1 ),
  1253. prev_selected_netcodes.end() );
  1254. m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings()->SetHighlight( false );
  1255. for( int& i : prev_selected_netcodes )
  1256. m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings()->SetHighlight( true, i, true );
  1257. m_frame->GetCanvas()->GetView()->UpdateAllLayersColor();
  1258. m_frame->GetCanvas()->Refresh();
  1259. m_in_build_nets_list = false;
  1260. }
  1261. void DIALOG_NET_INSPECTOR::onFilterChange( wxCommandEvent& aEvent )
  1262. {
  1263. wxStringTokenizer filters( m_textCtrlFilter->GetValue().Upper(), "," );
  1264. m_netFilter.clear();
  1265. while( filters.HasMoreTokens() )
  1266. {
  1267. wxString t = filters.GetNextToken();
  1268. t.Trim( false );
  1269. t.Trim( true );
  1270. if( !t.IsEmpty() )
  1271. {
  1272. m_netFilter.emplace_back( std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
  1273. m_netFilter.back()->SetPattern( t );
  1274. }
  1275. }
  1276. wxStringTokenizer group_filters( m_groupByText->GetValue(), "," );
  1277. m_groupFilter.clear();
  1278. while( group_filters.HasMoreTokens() )
  1279. {
  1280. wxString t = group_filters.GetNextToken();
  1281. t.Trim( false );
  1282. t.Trim( true );
  1283. if( !t.IsEmpty() )
  1284. {
  1285. if( m_groupByKind->GetSelection() == 0 || m_groupByKind->GetSelection() == 2 )
  1286. {
  1287. // type 2: wildcard match, use the matching substring as a group key.
  1288. // the number of groups is determined dynamically by the number of
  1289. // resulting matches in the whole set.
  1290. m_groupFilter.emplace_back( std::make_unique<EDA_PATTERN_MATCH_WILDCARD>() );
  1291. m_groupFilter.back()->SetPattern( t );
  1292. }
  1293. else if( m_groupByKind->GetSelection() == 1 || m_groupByKind->GetSelection() == 3 )
  1294. {
  1295. // type 3: regex match, use the matching substring as a group key.
  1296. // the number of groups is determined dynamically by the number of
  1297. // resulting matches in the whole set.
  1298. m_groupFilter.emplace_back( std::make_unique<EDA_PATTERN_MATCH_REGEX>() );
  1299. m_groupFilter.back()->SetPattern( t );
  1300. }
  1301. }
  1302. }
  1303. if( !m_filter_change_no_rebuild )
  1304. buildNetsList();
  1305. }
  1306. void DIALOG_NET_INSPECTOR::onSelChanged( wxDataViewEvent& )
  1307. {
  1308. onSelChanged();
  1309. }
  1310. void DIALOG_NET_INSPECTOR::onSelChanged()
  1311. {
  1312. // ignore selection changes while the whole list is being rebuilt.
  1313. if( m_in_build_nets_list )
  1314. return;
  1315. RENDER_SETTINGS* ps = m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings();
  1316. bool enable_rename_button = false;
  1317. bool enable_delete_button = false;
  1318. if( m_netsList->HasSelection() )
  1319. {
  1320. wxDataViewItemArray sel;
  1321. m_netsList->GetSelections( sel );
  1322. ps->SetHighlight( false );
  1323. enable_rename_button = sel.GetCount() == 1;
  1324. enable_delete_button = true;
  1325. for( unsigned int i = 0; i < sel.GetCount(); ++i )
  1326. {
  1327. const LIST_ITEM* ii = static_cast<const LIST_ITEM*>( sel.Item( i ).GetID() );
  1328. if( ii->GetIsGroup() )
  1329. {
  1330. enable_rename_button = false;
  1331. for( auto c = ii->ChildrenBegin(), end = ii->ChildrenEnd(); c != end; ++c )
  1332. ps->SetHighlight( true, ( *c )->GetNetCode(), true );
  1333. }
  1334. else
  1335. ps->SetHighlight( true, ii->GetNetCode(), true );
  1336. }
  1337. }
  1338. else
  1339. ps->SetHighlight( false );
  1340. m_frame->GetCanvas()->GetView()->UpdateAllLayersColor();
  1341. m_frame->GetCanvas()->Refresh();
  1342. m_renameNet->Enable( enable_rename_button );
  1343. m_deleteNet->Enable( enable_delete_button );
  1344. }
  1345. void DIALOG_NET_INSPECTOR::onSortingChanged( wxDataViewEvent& aEvent )
  1346. {
  1347. // FIXME: Whenever the sort criteria changes (sorting column)
  1348. // the visible row-numers of the selection get preserved, not the actual
  1349. // elements. Don't know at the moment how to preserve the selection,
  1350. // so just clear it for now.
  1351. m_netsList->UnselectAll();
  1352. m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings()->SetHighlight( false );
  1353. m_frame->GetCanvas()->GetView()->UpdateAllLayersColor();
  1354. m_frame->GetCanvas()->Refresh();
  1355. }
  1356. void DIALOG_NET_INSPECTOR::adjustListColumns()
  1357. {
  1358. /**
  1359. * Calculating optimal width of the first (Net) and the last (Pad Count) columns.
  1360. * That width must be enough to fit column header label and be not less than width of
  1361. * four chars (0000).
  1362. */
  1363. wxClientDC dc( GetParent() );
  1364. int h, minw, minw_col0, minw_col1;
  1365. int w0, w1, w2, w3, w4, w5, w6, w7;
  1366. dc.GetTextExtent( COLUMN_NET.display_name, &w0, &h );
  1367. dc.GetTextExtent( "MMMMMMMMMMMMMMMM", &minw_col1, &h );
  1368. dc.GetTextExtent( COLUMN_PAD_COUNT.display_name, &w2, &h );
  1369. dc.GetTextExtent( COLUMN_VIA_COUNT.display_name, &w3, &h );
  1370. dc.GetTextExtent( COLUMN_VIA_LENGTH.display_name, &w4, &h );
  1371. dc.GetTextExtent( COLUMN_BOARD_LENGTH.display_name, &w5, &h );
  1372. dc.GetTextExtent( COLUMN_CHIP_LENGTH.display_name, &w6, &h );
  1373. dc.GetTextExtent( COLUMN_TOTAL_LENGTH.display_name, &w7, &h );
  1374. dc.GetTextExtent( "00000,000 mm", &minw, &h );
  1375. dc.GetTextExtent( "00000", &minw_col0, &h );
  1376. // Considering left and right margins.
  1377. // For wxRenderGeneric it is 5px.
  1378. // Also account for the sorting arrow in the column header.
  1379. // Column 0 also needs space for any potential expander icons.
  1380. const int extra_width = 30;
  1381. w0 = std::max( w0, minw_col0 ) + extra_width;
  1382. minw_col1 = minw_col1 + extra_width;
  1383. w2 = w2 + extra_width;
  1384. w3 = w3 + extra_width;
  1385. w4 = std::max( w4 + extra_width, minw );
  1386. w5 = std::max( w5 + extra_width, minw );
  1387. w6 = std::max( w6 + extra_width, minw );
  1388. w7 = std::max( w7 + extra_width, minw );
  1389. // the columns might have been reordered. we work on the column model numbers though.
  1390. std::vector<int> column_order( m_data_model->columnCount() );
  1391. for( unsigned int i = 0; i < column_order.size(); ++i )
  1392. column_order[m_netsList->GetColumn( i )->GetModelColumn()] = i;
  1393. assert( column_order.size() == 8 );
  1394. m_netsList->GetColumn( column_order[0] )->SetWidth( w0 );
  1395. m_netsList->GetColumn( column_order[1] )->SetMinWidth( minw_col1 );
  1396. m_netsList->GetColumn( column_order[2] )->SetWidth( w2 );
  1397. m_netsList->GetColumn( column_order[3] )->SetWidth( w3 );
  1398. m_netsList->GetColumn( column_order[4] )->SetWidth( w4 );
  1399. m_netsList->GetColumn( column_order[5] )->SetWidth( w5 );
  1400. m_netsList->GetColumn( column_order[6] )->SetWidth( w6 );
  1401. m_netsList->GetColumn( column_order[7] )->SetWidth( w7 );
  1402. // At resizing of the list the width of middle column (Net Names) changes only.
  1403. int width = m_netsList->GetClientSize().x - 24;
  1404. w1 = width - w0 - w2 - w3 - w4 - w5 - w6 - w7;
  1405. if( w1 > minw_col1 )
  1406. m_netsList->GetColumn( column_order[1] )->SetWidth( w1 );
  1407. m_netsList->Refresh();
  1408. }
  1409. void DIALOG_NET_INSPECTOR::onListSize( wxSizeEvent& aEvent )
  1410. {
  1411. aEvent.Skip();
  1412. adjustListColumns();
  1413. }
  1414. void DIALOG_NET_INSPECTOR::onAddNet( wxCommandEvent& aEvent )
  1415. {
  1416. wxString newNetName;
  1417. NETNAME_VALIDATOR validator( &newNetName );
  1418. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Net name:" ), _( "New Net" ), newNetName );
  1419. dlg.SetTextValidator( validator );
  1420. while( true )
  1421. {
  1422. if( dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty() )
  1423. return; //Aborted by user
  1424. newNetName = dlg.GetValue();
  1425. if( m_brd->FindNet( newNetName ) )
  1426. {
  1427. DisplayError( this, wxString::Format( _( "Net name '%s' is already in use." ),
  1428. newNetName ) );
  1429. newNetName = wxEmptyString;
  1430. }
  1431. else
  1432. {
  1433. break;
  1434. }
  1435. }
  1436. NETINFO_ITEM *newnet = new NETINFO_ITEM( m_brd, dlg.GetValue(), 0 );
  1437. m_brd->Add( newnet );
  1438. m_frame->OnModify();
  1439. // We'll get an OnBoardItemAdded callback from this to update our listbox
  1440. }
  1441. void DIALOG_NET_INSPECTOR::onRenameNet( wxCommandEvent& aEvent )
  1442. {
  1443. if( m_netsList->GetSelectedItemsCount() == 1 )
  1444. {
  1445. const LIST_ITEM* sel = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  1446. if( sel->GetIsGroup() )
  1447. return;
  1448. NETINFO_ITEM* net = sel->GetNet();
  1449. wxString fullNetName = net->GetNetname();
  1450. wxString netPath;
  1451. wxString shortNetName;
  1452. if( fullNetName.Contains( "/" ) )
  1453. {
  1454. netPath = fullNetName.BeforeLast( '/' ) + '/';
  1455. shortNetName = fullNetName.AfterLast( '/' );
  1456. }
  1457. else
  1458. {
  1459. shortNetName = fullNetName;
  1460. }
  1461. wxString unescapedShortName = UnescapeString( shortNetName );
  1462. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Net name:" ), _( "Rename Net" ), unescapedShortName );
  1463. NETNAME_VALIDATOR validator( &unescapedShortName );
  1464. dlg.SetTextValidator( validator );
  1465. while( true )
  1466. {
  1467. if( dlg.ShowModal() != wxID_OK || dlg.GetValue() == unescapedShortName )
  1468. return;
  1469. unescapedShortName = dlg.GetValue();
  1470. if( unescapedShortName.IsEmpty() )
  1471. {
  1472. DisplayError( this, wxString::Format( _( "Net name cannot be empty." ),
  1473. unescapedShortName ) );
  1474. continue;
  1475. }
  1476. shortNetName = EscapeString( unescapedShortName, CTX_NETNAME );
  1477. fullNetName = netPath + shortNetName;
  1478. if( m_brd->FindNet( shortNetName ) || m_brd->FindNet( fullNetName ) )
  1479. {
  1480. DisplayError( this, wxString::Format( _( "Net name '%s' is already in use." ),
  1481. unescapedShortName ) );
  1482. unescapedShortName = wxEmptyString;
  1483. }
  1484. else
  1485. {
  1486. break;
  1487. }
  1488. }
  1489. // the changed name might require re-grouping. remove and re-insert
  1490. // is easier.
  1491. auto removed_item = m_data_model->deleteItem( m_data_model->findItem( net ) );
  1492. m_brd->GetNetInfo().RemoveNet( net );
  1493. net->SetNetname( fullNetName );
  1494. m_brd->GetNetInfo().AppendNet( net );
  1495. m_frame->OnModify();
  1496. if( netFilterMatches( net ) )
  1497. {
  1498. std::unique_ptr<LIST_ITEM> new_item = std::make_unique<LIST_ITEM>( net );
  1499. new_item->SetPadCount( removed_item->GetPadCount() );
  1500. new_item->SetViaCount( removed_item->GetViaCount() );
  1501. new_item->SetBoardWireLength( removed_item->GetBoardWireLength() );
  1502. new_item->SetChipWireLength( removed_item->GetChipWireLength() );
  1503. OPT<LIST_ITEM_ITER> added_row = m_data_model->addItem( std::move( new_item ) );
  1504. wxDataViewItemArray new_sel;
  1505. new_sel.Add( wxDataViewItem( &***added_row ) );
  1506. m_netsList->SetSelections( new_sel );
  1507. onSelChanged();
  1508. }
  1509. // Currently only tracks and pads have netname annotations and need to be redrawn,
  1510. // but zones are likely to follow. Since we don't have a way to ask what is current,
  1511. // just refresh all items.
  1512. m_frame->GetCanvas()->GetView()->UpdateAllItems( KIGFX::REPAINT );
  1513. m_frame->GetCanvas()->Refresh();
  1514. }
  1515. }
  1516. void DIALOG_NET_INSPECTOR::onDeleteNet( wxCommandEvent& aEvent )
  1517. {
  1518. if( !m_netsList->HasSelection() )
  1519. return;
  1520. wxDataViewItemArray sel;
  1521. m_netsList->GetSelections( sel );
  1522. auto delete_one =
  1523. [this]( const LIST_ITEM* i )
  1524. {
  1525. if( i->GetPadCount() == 0
  1526. || IsOK( this, wxString::Format( _( "Net '%s' is in use. Delete anyway?" ),
  1527. i->GetNetName() ) ) )
  1528. {
  1529. // This is a bit hacky, but it will do for now, since this is the only path
  1530. // outside the netlist updater where you can remove a net from a BOARD.
  1531. int removedCode = i->GetNetCode();
  1532. m_frame->GetCanvas()->GetView()->UpdateAllItemsConditionally( KIGFX::REPAINT,
  1533. [removedCode]( KIGFX::VIEW_ITEM* aItem ) -> bool
  1534. {
  1535. if( auto bci = dynamic_cast<BOARD_CONNECTED_ITEM*>( aItem ) )
  1536. return bci->GetNetCode() == removedCode;
  1537. return false;
  1538. } );
  1539. m_brd->Remove( i->GetNet() );
  1540. m_frame->OnModify();
  1541. // We'll get an OnBoardItemRemoved callback from this to update our listbox
  1542. }
  1543. };
  1544. for( unsigned int i = 0; i < sel.GetCount(); ++i )
  1545. {
  1546. const LIST_ITEM* ii = static_cast<const LIST_ITEM*>( sel.Item( i ).GetID() );
  1547. if( ii->GetIsGroup() )
  1548. {
  1549. if( ii->ChildrenCount() != 0
  1550. && IsOK( this, wxString::Format( _( "Delete all nets in group '%s'?" ),
  1551. ii->GetGroupName() ) ) )
  1552. {
  1553. // we can't be iterating the children container and deleting items from
  1554. // it at the same time. thus take a copy of it first.
  1555. std::vector<const LIST_ITEM*> children;
  1556. children.reserve( ii->ChildrenCount() );
  1557. std::copy( ii->ChildrenBegin(), ii->ChildrenEnd(), std::back_inserter( children ) );
  1558. for( const LIST_ITEM* c : children )
  1559. delete_one( c );
  1560. }
  1561. }
  1562. else
  1563. {
  1564. delete_one( ii );
  1565. }
  1566. }
  1567. }
  1568. void DIALOG_NET_INSPECTOR::onReport( wxCommandEvent& aEvent )
  1569. {
  1570. wxFileDialog dlg( this, _( "Report file" ), "", "",
  1571. _( "Report file" ) + AddFileExtListToFilter( { "csv" } ),
  1572. wxFD_SAVE );
  1573. if( dlg.ShowModal() == wxID_CANCEL )
  1574. return;
  1575. wxTextFile f( dlg.GetPath() );
  1576. f.Create();
  1577. wxString txt;
  1578. // Print Header:
  1579. for( auto&& col : m_data_model->columnDesc() )
  1580. txt += '"' + col.csv_name + "\";";
  1581. f.AddLine( txt );
  1582. // Print list of nets:
  1583. const unsigned int num_rows = m_data_model->itemCount();
  1584. for( unsigned int row = 0; row < num_rows; row++ )
  1585. {
  1586. auto& i = m_data_model->itemAt( row );
  1587. if( i.GetIsGroup() || i.GetNetCode() == 0 )
  1588. continue;
  1589. txt = "";
  1590. for( auto&& col : m_data_model->columnDesc() )
  1591. {
  1592. if( col.csv_flags & COLUMN_DESC::CSV_QUOTE )
  1593. txt += '"' + m_data_model->valueAt( col.num, row ).GetString() + "\";";
  1594. else
  1595. txt += m_data_model->valueAt( col.num, row ).GetString() + ';';
  1596. }
  1597. f.AddLine( txt );
  1598. }
  1599. f.Write();
  1600. f.Close();
  1601. }