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.

1980 lines
64 KiB

10 months ago
1 year ago
10 months ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
1 year ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright The KiCad Developers, see AUTHORS.txt for contributors.
  5. *
  6. * This program is free software: you can redistribute it and/or modify it
  7. * under the terms of the GNU General Public License as published by the
  8. * Free Software Foundation, either version 3 of the License, or (at your
  9. * option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful, but
  12. * WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along
  17. * with this program. If not, see <http://www.gnu.org/licenses/>.
  18. */
  19. #include <widgets/pcb_net_inspector_panel.h>
  20. #include <widgets/pcb_net_inspector_panel_data_model.h>
  21. #include <board_design_settings.h>
  22. #include <board_stackup_manager/board_stackup.h>
  23. #include <confirm.h>
  24. #include <connectivity/connectivity_algo.h>
  25. #include <dialogs/dialog_text_entry.h>
  26. #include <footprint.h>
  27. #include <pad.h>
  28. #include <pcb_edit_frame.h>
  29. #include <pcb_painter.h>
  30. #include <pcb_track.h>
  31. #include <pgm_base.h>
  32. #include <settings/settings_manager.h>
  33. #include <validators.h>
  34. #include <wildcards_and_files_ext.h>
  35. #include <eda_pattern_match.h>
  36. #include <wx/wupdlock.h>
  37. #include <wx/filedlg.h>
  38. #include <algorithm>
  39. PCB_NET_INSPECTOR_PANEL::PCB_NET_INSPECTOR_PANEL( wxWindow* parent, PCB_EDIT_FRAME* aFrame ) :
  40. NET_INSPECTOR_PANEL( parent, aFrame ),
  41. m_zero_netitem( nullptr ),
  42. m_frame( aFrame )
  43. {
  44. m_brd = m_frame->GetBoard();
  45. m_data_model = new DATA_MODEL( *this );
  46. m_netsList->AssociateModel( &*m_data_model );
  47. // Rebuild nets list
  48. buildNetsList( true );
  49. // Register the panel to receive board change notifications
  50. if( m_brd != nullptr )
  51. {
  52. OnBoardHighlightNetChanged( *m_brd );
  53. m_brd->AddListener( this );
  54. }
  55. // Connect to board events
  56. m_frame->Bind( EDA_EVT_UNITS_CHANGED, &PCB_NET_INSPECTOR_PANEL::onUnitsChanged, this );
  57. // Connect to wxDataViewCtrl events
  58. m_netsList->Bind( wxEVT_DATAVIEW_ITEM_EXPANDED, &PCB_NET_INSPECTOR_PANEL::OnExpandCollapseRow,
  59. this );
  60. m_netsList->Bind( wxEVT_DATAVIEW_ITEM_COLLAPSED, &PCB_NET_INSPECTOR_PANEL::OnExpandCollapseRow,
  61. this );
  62. m_netsList->Bind( wxEVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK,
  63. &PCB_NET_INSPECTOR_PANEL::OnHeaderContextMenu, this );
  64. m_netsList->Bind( wxEVT_DATAVIEW_ITEM_CONTEXT_MENU,
  65. &PCB_NET_INSPECTOR_PANEL::OnNetsListContextMenu, this );
  66. m_netsList->Bind( wxEVT_DATAVIEW_ITEM_ACTIVATED,
  67. &PCB_NET_INSPECTOR_PANEL::OnNetsListItemActivated, this );
  68. m_netsList->Bind( wxEVT_DATAVIEW_COLUMN_SORTED,
  69. &PCB_NET_INSPECTOR_PANEL::OnColumnSorted, this );
  70. }
  71. PCB_NET_INSPECTOR_PANEL::~PCB_NET_INSPECTOR_PANEL()
  72. {
  73. SaveSettings();
  74. m_netsList->AssociateModel( nullptr );
  75. // Disconnect from board events
  76. m_frame->Unbind( EDA_EVT_UNITS_CHANGED, &PCB_NET_INSPECTOR_PANEL::onUnitsChanged, this );
  77. // Connect to wxDataViewCtrl events
  78. m_netsList->Unbind( wxEVT_DATAVIEW_ITEM_EXPANDED, &PCB_NET_INSPECTOR_PANEL::OnExpandCollapseRow,
  79. this );
  80. m_netsList->Unbind( wxEVT_DATAVIEW_ITEM_COLLAPSED,
  81. &PCB_NET_INSPECTOR_PANEL::OnExpandCollapseRow, this );
  82. m_netsList->Unbind( wxEVT_DATAVIEW_COLUMN_HEADER_RIGHT_CLICK,
  83. &PCB_NET_INSPECTOR_PANEL::OnHeaderContextMenu, this );
  84. m_netsList->Unbind( wxEVT_DATAVIEW_ITEM_CONTEXT_MENU,
  85. &PCB_NET_INSPECTOR_PANEL::OnNetsListContextMenu, this );
  86. m_netsList->Unbind( wxEVT_DATAVIEW_ITEM_ACTIVATED,
  87. &PCB_NET_INSPECTOR_PANEL::OnNetsListItemActivated, this );
  88. m_netsList->Unbind( wxEVT_DATAVIEW_COLUMN_SORTED,
  89. &PCB_NET_INSPECTOR_PANEL::OnColumnSorted, this );
  90. }
  91. /*****************************************************************************************
  92. *
  93. * Grid / model columns configuration
  94. *
  95. * ***************************************************************************************/
  96. void PCB_NET_INSPECTOR_PANEL::buildColumns()
  97. {
  98. m_columns.clear();
  99. // Set up the column display vector
  100. m_columns.emplace_back( 0u, UNDEFINED_LAYER, _( "Name" ), _( "Net Name" ),
  101. CSV_COLUMN_DESC::CSV_QUOTE, false );
  102. m_columns.emplace_back( 1u, UNDEFINED_LAYER, _( "Netclass" ), _( "Netclass" ),
  103. CSV_COLUMN_DESC::CSV_QUOTE, false );
  104. m_columns.emplace_back( 2u, UNDEFINED_LAYER, _( "Total Length" ), _( "Net Length" ),
  105. CSV_COLUMN_DESC::CSV_NONE, true );
  106. m_columns.emplace_back( 3u, UNDEFINED_LAYER, _( "Via Count" ), _( "Via Count" ),
  107. CSV_COLUMN_DESC::CSV_NONE, false );
  108. m_columns.emplace_back( 4u, UNDEFINED_LAYER, _( "Via Length" ), _( "Via Length" ),
  109. CSV_COLUMN_DESC::CSV_NONE, true );
  110. m_columns.emplace_back( 5u, UNDEFINED_LAYER, _( "Track Length" ), _( "Track Length" ),
  111. CSV_COLUMN_DESC::CSV_NONE, true );
  112. m_columns.emplace_back( 6u, UNDEFINED_LAYER, _( "Die Length" ), _( "Die Length" ),
  113. CSV_COLUMN_DESC::CSV_NONE, true );
  114. m_columns.emplace_back( 7u, UNDEFINED_LAYER, _( "Pad Count" ), _( "Pad Count" ),
  115. CSV_COLUMN_DESC::CSV_NONE, false );
  116. std::vector<std::function<void( void )>> add_col{
  117. [&]()
  118. {
  119. m_netsList->AppendTextColumn( m_columns[COLUMN_NAME].display_name,
  120. m_columns[COLUMN_NAME], wxDATAVIEW_CELL_INERT, -1,
  121. wxALIGN_LEFT,
  122. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_SORTABLE );
  123. },
  124. [&]()
  125. {
  126. m_netsList->AppendTextColumn( m_columns[COLUMN_NETCLASS].display_name,
  127. m_columns[COLUMN_NETCLASS], wxDATAVIEW_CELL_INERT, -1,
  128. wxALIGN_LEFT,
  129. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  130. | wxDATAVIEW_COL_SORTABLE );
  131. },
  132. [&]()
  133. {
  134. m_netsList->AppendTextColumn( m_columns[COLUMN_TOTAL_LENGTH].display_name,
  135. m_columns[COLUMN_TOTAL_LENGTH], wxDATAVIEW_CELL_INERT, -1,
  136. wxALIGN_CENTER,
  137. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  138. | wxDATAVIEW_COL_SORTABLE );
  139. },
  140. [&]()
  141. {
  142. m_netsList->AppendTextColumn( m_columns[COLUMN_VIA_COUNT].display_name,
  143. m_columns[COLUMN_VIA_COUNT], wxDATAVIEW_CELL_INERT, -1,
  144. wxALIGN_CENTER,
  145. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  146. | wxDATAVIEW_COL_SORTABLE );
  147. },
  148. [&]()
  149. {
  150. m_netsList->AppendTextColumn( m_columns[COLUMN_VIA_LENGTH].display_name,
  151. m_columns[COLUMN_VIA_LENGTH], wxDATAVIEW_CELL_INERT, -1,
  152. wxALIGN_CENTER,
  153. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  154. | wxDATAVIEW_COL_SORTABLE );
  155. },
  156. [&]()
  157. {
  158. m_netsList->AppendTextColumn( m_columns[COLUMN_BOARD_LENGTH].display_name,
  159. m_columns[COLUMN_BOARD_LENGTH], wxDATAVIEW_CELL_INERT, -1,
  160. wxALIGN_CENTER,
  161. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  162. | wxDATAVIEW_COL_SORTABLE );
  163. },
  164. [&]()
  165. {
  166. m_netsList->AppendTextColumn( m_columns[COLUMN_PAD_DIE_LENGTH].display_name,
  167. m_columns[COLUMN_PAD_DIE_LENGTH], wxDATAVIEW_CELL_INERT,
  168. -1, wxALIGN_CENTER,
  169. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  170. | wxDATAVIEW_COL_SORTABLE );
  171. },
  172. [&]()
  173. {
  174. m_netsList->AppendTextColumn( m_columns[COLUMN_PAD_COUNT].display_name,
  175. m_columns[COLUMN_PAD_COUNT], wxDATAVIEW_CELL_INERT, -1,
  176. wxALIGN_CENTER,
  177. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  178. | wxDATAVIEW_COL_SORTABLE );
  179. }
  180. };
  181. // If we have not yet loaded the first board, use a dummy local settings object to ensure we
  182. // don't over-write existing board settings (note that PCB_EDIT_FRAME loads the local settings
  183. // object prior to loading the board; the two are not synced and we need to account for that)
  184. PANEL_NET_INSPECTOR_SETTINGS* cfg = nullptr;
  185. if( m_board_loaded )
  186. {
  187. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  188. cfg = &localSettings.m_NetInspectorPanel;
  189. }
  190. else
  191. {
  192. cfg = new PANEL_NET_INSPECTOR_SETTINGS();
  193. }
  194. // Count number of copper layers
  195. m_num_copper_layers = 0;
  196. for( PCB_LAYER_ID layer : m_brd->GetEnabledLayers().Seq() )
  197. {
  198. if( IsCopperLayer( layer ) )
  199. ++m_num_copper_layers;
  200. }
  201. // Reset the column display settings if column count doesn't match
  202. const int totalNumColumns = add_col.size() + m_num_copper_layers;
  203. if( (int) cfg->col_order.size() != totalNumColumns
  204. || (int) cfg->col_hidden.size() != totalNumColumns )
  205. {
  206. cfg->col_order.resize( totalNumColumns );
  207. cfg->col_hidden.resize( totalNumColumns );
  208. for( int i = 0; i < totalNumColumns; ++i )
  209. {
  210. cfg->col_order[i] = i;
  211. cfg->col_hidden[i] = false;
  212. }
  213. }
  214. // Check that all rows are unique to protect against corrupted settings data
  215. std::set<int> col_order_set( cfg->col_order.begin(), cfg->col_order.end() );
  216. if( col_order_set.size() != cfg->col_order.size() )
  217. {
  218. for( std::size_t i = 0; i < cfg->col_order.size(); ++i )
  219. cfg->col_order[i] = i;
  220. }
  221. // Add column records for copper layers
  222. for( PCB_LAYER_ID layer : m_brd->GetEnabledLayers().Seq() )
  223. {
  224. if( !IsCopperLayer( layer ) )
  225. continue;
  226. m_columns.emplace_back( m_columns.size(), layer, m_brd->GetLayerName( layer ),
  227. m_brd->GetLayerName( layer ), CSV_COLUMN_DESC::CSV_NONE, true );
  228. }
  229. // Add display columns in settings order
  230. for( std::size_t i = 0; i < cfg->col_order.size(); ++i )
  231. {
  232. const int addModelColumn = cfg->col_order[i];
  233. if( addModelColumn >= (int) add_col.size() )
  234. {
  235. m_netsList->AppendTextColumn( m_brd->GetLayerName( m_columns[addModelColumn].layer ),
  236. m_columns[addModelColumn], wxDATAVIEW_CELL_INERT, -1,
  237. wxALIGN_CENTER,
  238. wxDATAVIEW_COL_RESIZABLE | wxDATAVIEW_COL_REORDERABLE
  239. | wxDATAVIEW_COL_SORTABLE );
  240. }
  241. else
  242. {
  243. add_col.at( cfg->col_order[i] )();
  244. }
  245. }
  246. // Set the name column as the expander row
  247. if( wxDataViewColumn* col = getDisplayedColumnForModelField( COLUMN_NAME ) )
  248. {
  249. m_netsList->SetExpanderColumn( col );
  250. }
  251. adjustListColumnSizes( cfg );
  252. // Delete the temporary config if used
  253. if( !m_board_loaded )
  254. {
  255. delete cfg;
  256. }
  257. }
  258. void PCB_NET_INSPECTOR_PANEL::adjustListColumnSizes( PANEL_NET_INSPECTOR_SETTINGS* cfg )
  259. {
  260. wxWindowUpdateLocker locker( m_netsList );
  261. if( cfg->col_widths.size() != m_columns.size() )
  262. {
  263. int minValueWidth = GetTextExtent( wxT( "00000,000 mm" ) ).x;
  264. int minNumberWidth = GetTextExtent( wxT( "000" ) ).x;
  265. int minNameWidth = GetTextExtent( wxT( "MMMMMMMMMMMM" ) ).x;
  266. // Considering left and right margins.
  267. // For wxRenderGeneric it is 5px.
  268. // Also account for the sorting arrow in the column header.
  269. // Column 0 also needs space for any potential expander icons.
  270. const int margins = 15;
  271. const int extra_width = 30;
  272. auto getTargetWidth =
  273. [&]( int columnID )
  274. {
  275. switch( columnID )
  276. {
  277. case COLUMN_NAME: return minNameWidth + extra_width;
  278. case COLUMN_NETCLASS: return minNameWidth + margins;
  279. case COLUMN_VIA_COUNT: return minNumberWidth + margins;
  280. case COLUMN_PAD_COUNT: return minNumberWidth + margins;
  281. default: return minValueWidth + margins;
  282. }
  283. };
  284. wxASSERT( m_columns.size() == cfg->col_order.size() );
  285. for( size_t i = 0; i < m_columns.size(); ++i )
  286. {
  287. const int modelColumn = cfg->col_order[i];
  288. int titleSize = GetTextExtent( m_columns[modelColumn].display_name ).x;
  289. titleSize = modelColumn == COLUMN_NAME ? titleSize + extra_width : titleSize + margins;
  290. const int valSize = getTargetWidth( modelColumn );
  291. m_netsList->GetColumn( i )->SetWidth( std::max( titleSize, valSize ) );
  292. }
  293. }
  294. else
  295. {
  296. wxASSERT( m_columns.size() == cfg->col_hidden.size() );
  297. wxASSERT( m_columns.size() == cfg->col_widths.size() );
  298. for( size_t ii = 0; ii < m_columns.size(); ++ii )
  299. {
  300. const int newWidth = cfg->col_widths[ii];
  301. // Make sure we end up with something non-zero so we can resize it
  302. m_netsList->GetColumn( ii )->SetWidth( std::max( newWidth, 10 ) );
  303. m_netsList->GetColumn( ii )->SetHidden( cfg->col_hidden[ii] );
  304. }
  305. }
  306. m_netsList->Refresh();
  307. }
  308. bool PCB_NET_INSPECTOR_PANEL::restoreSortColumn( int sortingColumnId, bool sortOrderAsc )
  309. {
  310. if( sortingColumnId != -1 )
  311. {
  312. if( wxDataViewColumn* col = getDisplayedColumnForModelField( sortingColumnId ) )
  313. {
  314. col->SetSortOrder( sortOrderAsc );
  315. m_data_model->Resort();
  316. return true;
  317. }
  318. }
  319. return false;
  320. }
  321. wxDataViewColumn* PCB_NET_INSPECTOR_PANEL::getDisplayedColumnForModelField( int columnId )
  322. {
  323. for( unsigned int i = 0; i < m_netsList->GetColumnCount(); ++i )
  324. {
  325. wxDataViewColumn* col = m_netsList->GetColumn( i );
  326. if( (int) col->GetModelColumn() == columnId )
  327. {
  328. return col;
  329. }
  330. }
  331. return nullptr;
  332. }
  333. /*****************************************************************************************
  334. *
  335. * Nets list generation
  336. *
  337. * ***************************************************************************************/
  338. void PCB_NET_INSPECTOR_PANEL::buildNetsList( bool rebuildColumns )
  339. {
  340. // Only build the list of nets if there is a board present
  341. if( !m_brd )
  342. return;
  343. m_in_build_nets_list = true;
  344. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  345. PANEL_NET_INSPECTOR_SETTINGS* cfg = &localSettings.m_NetInspectorPanel;
  346. // Refresh all filtering / grouping settings
  347. m_filter_by_net_name = cfg->filter_by_net_name;
  348. m_filter_by_netclass = cfg->filter_by_netclass;
  349. m_show_zero_pad_nets = cfg->show_zero_pad_nets;
  350. m_group_by_netclass = cfg->group_by_netclass;
  351. m_group_by_constraint = cfg->group_by_constraint;
  352. // Attempt to keep any expanded groups open
  353. if( m_board_loaded && !m_board_loading )
  354. {
  355. cfg->expanded_rows.clear();
  356. DATA_MODEL* model = static_cast<DATA_MODEL*>( m_netsList->GetModel() );
  357. for( const auto& groupItems = model->getGroupDataViewItems();
  358. auto& [groupName, groupItem] : groupItems )
  359. {
  360. if( m_netsList->IsExpanded( groupItem ) )
  361. cfg->expanded_rows.push_back( groupName );
  362. }
  363. }
  364. // when rebuilding the netlist, try to keep the row selection
  365. wxDataViewItemArray sel;
  366. m_netsList->GetSelections( sel );
  367. std::vector<int> prev_selected_netcodes;
  368. prev_selected_netcodes.reserve( sel.GetCount() );
  369. for( unsigned int i = 0; i < sel.GetCount(); ++i )
  370. {
  371. const LIST_ITEM* item = static_cast<const LIST_ITEM*>( sel.Item( i ).GetID() );
  372. prev_selected_netcodes.push_back( item->GetNetCode() );
  373. }
  374. int sorting_column_id = cfg->sorting_column;
  375. bool sort_order_asc = cfg->sort_order_asc;
  376. if( wxDataViewColumn* sorting_column = m_netsList->GetSortingColumn() )
  377. {
  378. if( !m_board_loading )
  379. {
  380. sorting_column_id = static_cast<int>( sorting_column->GetModelColumn() );
  381. sort_order_asc = sorting_column->IsSortOrderAscending();
  382. }
  383. // On GTK, wxDVC will crash if you rebuild with a sorting column set.
  384. sorting_column->UnsetAsSortKey();
  385. }
  386. if( rebuildColumns )
  387. {
  388. m_netsList->ClearColumns();
  389. buildColumns();
  390. }
  391. m_data_model->deleteAllItems();
  392. m_custom_group_rules.clear();
  393. for( const wxString& rule : cfg->custom_group_rules )
  394. m_custom_group_rules.push_back( std::make_unique<EDA_COMBINED_MATCHER>( rule, CTX_NET ) );
  395. m_data_model->addCustomGroups();
  396. std::vector<std::unique_ptr<LIST_ITEM>> new_items;
  397. std::vector<CN_ITEM*> prefiltered_cn_items = relevantConnectivityItems();
  398. struct NET_INFO
  399. {
  400. int netcode;
  401. NETINFO_ITEM* net;
  402. unsigned int pad_count;
  403. };
  404. struct NET_INFO_CMP_LESS
  405. {
  406. bool operator()( const NET_INFO& a, const NET_INFO& b ) const
  407. {
  408. return a.netcode < b.netcode;
  409. }
  410. bool operator()( const NET_INFO& a, int b ) const { return a.netcode < b; }
  411. bool operator()( int a, const NET_INFO& b ) const { return a < b.netcode; }
  412. };
  413. std::vector<NET_INFO> nets;
  414. nets.reserve( m_brd->GetNetInfo().NetsByNetcode().size() );
  415. for( const std::pair<int, NETINFO_ITEM*> ni : m_brd->GetNetInfo().NetsByNetcode() )
  416. {
  417. if( ni.first == 0 )
  418. m_zero_netitem = ni.second;
  419. if( netFilterMatches( ni.second, cfg ) )
  420. nets.emplace_back( NET_INFO{ ni.first, ni.second, 0 } );
  421. }
  422. // count the pads for each net. since the nets are sorted by netcode
  423. // iterating over the footprints' pads is faster.
  424. for( FOOTPRINT* footprint : m_brd->Footprints() )
  425. {
  426. for( PAD* pad : footprint->Pads() )
  427. {
  428. auto i = std::lower_bound( nets.begin(), nets.end(), pad->GetNetCode(),
  429. NET_INFO_CMP_LESS() );
  430. if( i != nets.end() && i->netcode == pad->GetNetCode() )
  431. i->pad_count += 1;
  432. }
  433. }
  434. for( NET_INFO& ni : nets )
  435. {
  436. if( m_show_zero_pad_nets || ni.pad_count > 0 )
  437. new_items.emplace_back( buildNewItem( ni.net, ni.pad_count, prefiltered_cn_items ) );
  438. }
  439. m_data_model->addItems( std::move( new_items ) );
  440. // Re-enable the sorting column
  441. if( !restoreSortColumn( sorting_column_id, sort_order_asc ))
  442. {
  443. // By default sort by Name column
  444. restoreSortColumn( COLUMN_NAME, true );
  445. }
  446. // Try to restore the expanded groups
  447. if( m_board_loaded )
  448. {
  449. m_row_expanding = true;
  450. std::vector<std::pair<wxString, wxDataViewItem>> groupItems =
  451. m_data_model->getGroupDataViewItems();
  452. for( wxString& groupName : cfg->expanded_rows )
  453. {
  454. auto pred =
  455. [&groupName]( const std::pair<wxString, wxDataViewItem>& item )
  456. {
  457. return groupName == item.first;
  458. };
  459. auto tableItem = std::find_if( groupItems.begin(), groupItems.end(), pred );
  460. if( tableItem != groupItems.end() )
  461. m_netsList->Expand( tableItem->second );
  462. }
  463. m_row_expanding = false;
  464. }
  465. // try to restore the selected rows. Set the ones that we can't find any more to -1.
  466. sel.Clear();
  467. for( int& nc : prev_selected_netcodes )
  468. {
  469. std::optional<LIST_ITEM_ITER> r = m_data_model->findItem( nc );
  470. if( r )
  471. {
  472. const std::unique_ptr<LIST_ITEM>& list_item = *r.value();
  473. sel.Add( wxDataViewItem( list_item.get() ) );
  474. }
  475. else
  476. {
  477. nc = -1;
  478. }
  479. }
  480. if( !sel.IsEmpty() )
  481. {
  482. m_netsList->SetSelections( sel );
  483. m_netsList->EnsureVisible( sel.Item( 0 ) );
  484. }
  485. else
  486. {
  487. m_netsList->UnselectAll();
  488. }
  489. alg::delete_matching( prev_selected_netcodes, -1 );
  490. m_in_build_nets_list = false;
  491. }
  492. bool PCB_NET_INSPECTOR_PANEL::netFilterMatches( NETINFO_ITEM* aNet,
  493. PANEL_NET_INSPECTOR_SETTINGS* cfg ) const
  494. {
  495. if( cfg == nullptr )
  496. {
  497. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  498. cfg = &localSettings.m_NetInspectorPanel;
  499. }
  500. // Never show the unconnected net
  501. if( aNet->GetNetCode() <= 0 )
  502. return false;
  503. wxString filterString = UnescapeString( m_searchCtrl->GetValue() ).Upper();
  504. wxString netName = UnescapeString( aNet->GetNetname() ).Upper();
  505. NETCLASS* netClass = aNet->GetNetClass();
  506. wxString netClassName = UnescapeString( netClass->GetName() ).Upper();
  507. bool matched = false;
  508. // No filter - match all
  509. if( filterString.Length() == 0 )
  510. matched = true;
  511. // Search on Netclass
  512. if( !matched && cfg->filter_by_netclass && netClassName.Find( filterString ) != wxNOT_FOUND )
  513. matched = true;
  514. // Search on Net name
  515. if( !matched && cfg->filter_by_net_name && netName.Find( filterString ) != wxNOT_FOUND )
  516. matched = true;
  517. // Remove unconnected nets if required
  518. if( matched )
  519. {
  520. if( !m_show_unconnected_nets )
  521. matched = !netName.StartsWith( wxT( "UNCONNECTED-(" ) );
  522. }
  523. return matched;
  524. }
  525. struct NETCODE_CMP_LESS
  526. {
  527. bool operator()( const CN_ITEM* a, const CN_ITEM* b ) const { return a->Net() < b->Net(); }
  528. bool operator()( const CN_ITEM* a, int b ) const { return a->Net() < b; }
  529. bool operator()( int a, const CN_ITEM* b ) const { return a < b->Net(); }
  530. };
  531. std::unique_ptr<PCB_NET_INSPECTOR_PANEL::LIST_ITEM>
  532. PCB_NET_INSPECTOR_PANEL::buildNewItem( NETINFO_ITEM* aNet, unsigned int aPadCount,
  533. const std::vector<CN_ITEM*>& aCNItems )
  534. {
  535. std::unique_ptr<LIST_ITEM> new_item = std::make_unique<LIST_ITEM>( aNet );
  536. new_item->SetPadCount( aPadCount );
  537. new_item->SetLayerCount( m_brd->GetCopperLayerCount() );
  538. const auto cn_items = std::equal_range( aCNItems.begin(), aCNItems.end(), aNet->GetNetCode(),
  539. NETCODE_CMP_LESS() );
  540. for( auto i = cn_items.first; i != cn_items.second; ++i )
  541. {
  542. BOARD_CONNECTED_ITEM* item = ( *i )->Parent();
  543. if( item->Type() == PCB_PAD_T )
  544. {
  545. new_item->AddPadDieLength( static_cast<PAD*>( item )->GetPadToDieLength() );
  546. }
  547. else if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( item ) )
  548. {
  549. new_item->AddLayerWireLength( track->GetLength(), track->GetLayer() );
  550. if( item->Type() == PCB_VIA_T )
  551. {
  552. new_item->AddViaCount( 1 );
  553. new_item->AddViaLength( calculateViaLength( track ) );
  554. }
  555. }
  556. }
  557. return new_item;
  558. }
  559. std::vector<CN_ITEM*> PCB_NET_INSPECTOR_PANEL::relevantConnectivityItems() const
  560. {
  561. // pre-filter the connectivity items and sort them by netcode.
  562. // this avoids quadratic runtime when building the whole net list and
  563. // calculating the total length for each net.
  564. const auto type_bits = std::bitset<MAX_STRUCT_TYPE_ID>()
  565. .set( PCB_TRACE_T )
  566. .set( PCB_ARC_T )
  567. .set( PCB_VIA_T )
  568. .set( PCB_PAD_T );
  569. std::vector<CN_ITEM*> cn_items;
  570. cn_items.reserve( 1024 );
  571. for( CN_ITEM* cn_item : m_brd->GetConnectivity()->GetConnectivityAlgo()->ItemList() )
  572. {
  573. if( cn_item->Valid() && type_bits[cn_item->Parent()->Type()] )
  574. cn_items.push_back( cn_item );
  575. }
  576. std::sort( cn_items.begin(), cn_items.end(), NETCODE_CMP_LESS() );
  577. return cn_items;
  578. }
  579. unsigned int PCB_NET_INSPECTOR_PANEL::calculateViaLength( const PCB_TRACK* aTrack ) const
  580. {
  581. const PCB_VIA* via = dynamic_cast<const PCB_VIA*>( aTrack );
  582. if( !via )
  583. return 0;
  584. BOARD_DESIGN_SETTINGS& bds = m_brd->GetDesignSettings();
  585. // Must be static to keep from raising its ugly head in performance profiles
  586. static std::initializer_list<KICAD_T> traceAndPadTypes = { PCB_TRACE_T, PCB_ARC_T, PCB_PAD_T };
  587. // calculate the via length individually from the board stackup and via's start and end layer.
  588. if( bds.m_HasStackup )
  589. {
  590. PCB_LAYER_ID top_layer = UNDEFINED_LAYER;
  591. PCB_LAYER_ID bottom_layer = UNDEFINED_LAYER;
  592. LSET layers = bds.GetEnabledLayers();
  593. for( auto layer_it = layers.copper_layers_begin();
  594. layer_it != layers.copper_layers_end();
  595. ++layer_it )
  596. {
  597. if( m_brd->GetConnectivity()->IsConnectedOnLayer( via, *layer_it, traceAndPadTypes ) )
  598. {
  599. if( top_layer == UNDEFINED_LAYER )
  600. top_layer = PCB_LAYER_ID( *layer_it );
  601. else
  602. bottom_layer = PCB_LAYER_ID( *layer_it );
  603. }
  604. }
  605. if( top_layer == UNDEFINED_LAYER )
  606. top_layer = via->TopLayer();
  607. if( bottom_layer == UNDEFINED_LAYER )
  608. bottom_layer = via->BottomLayer();
  609. const BOARD_STACKUP& stackup = bds.GetStackupDescriptor();
  610. return stackup.GetLayerDistance( top_layer, bottom_layer );
  611. }
  612. else
  613. {
  614. int dielectricLayers = bds.GetCopperLayerCount() - 1;
  615. // TODO: not all dielectric layers are the same thickness!
  616. int layerThickness = bds.GetBoardThickness() / dielectricLayers;
  617. int effectiveBottomLayer;
  618. if( via->BottomLayer() == B_Cu )
  619. effectiveBottomLayer = F_Cu + dielectricLayers;
  620. else
  621. effectiveBottomLayer = via->BottomLayer();
  622. int layerCount = effectiveBottomLayer - via->TopLayer();
  623. return layerCount * layerThickness;
  624. }
  625. }
  626. void PCB_NET_INSPECTOR_PANEL::updateNet( NETINFO_ITEM* aNet )
  627. {
  628. // something for the specified net has changed, update that row.
  629. // ignore nets that are not in our list because the filter doesn't match.
  630. if( !netFilterMatches( aNet ) )
  631. {
  632. m_data_model->deleteItem( m_data_model->findItem( aNet ) );
  633. return;
  634. }
  635. // if the net had no pads before, it might not be in the displayed list yet.
  636. // if it had pads and now doesn't anymore, we might need to remove it from the list.
  637. std::optional<LIST_ITEM_ITER> cur_net_row = m_data_model->findItem( aNet );
  638. const unsigned int node_count = m_brd->GetNodesCount( aNet->GetNetCode() );
  639. if( node_count == 0 && !m_show_zero_pad_nets )
  640. {
  641. m_data_model->deleteItem( cur_net_row );
  642. return;
  643. }
  644. std::unique_ptr<LIST_ITEM> new_list_item = buildNewItem( aNet, node_count,
  645. relevantConnectivityItems() );
  646. if( !cur_net_row )
  647. {
  648. m_data_model->addItem( std::move( new_list_item ) );
  649. return;
  650. }
  651. const std::unique_ptr<LIST_ITEM>& cur_list_item = *cur_net_row.value();
  652. if( cur_list_item->GetNetName() != new_list_item->GetNetName() )
  653. {
  654. // if the name has changed, it might require re-grouping.
  655. // it's easier to remove and re-insert it
  656. m_data_model->deleteItem( cur_net_row );
  657. m_data_model->addItem( std::move( new_list_item ) );
  658. }
  659. else
  660. {
  661. // update fields only
  662. cur_list_item->SetPadCount( new_list_item->GetPadCount() );
  663. cur_list_item->SetViaCount( new_list_item->GetViaCount() );
  664. cur_list_item->SetLayerWireLength( new_list_item->GetLayerWireLength() );
  665. cur_list_item->SetPadDieLength( new_list_item->GetPadDieLength() );
  666. updateDisplayedRowValues( cur_net_row );
  667. }
  668. }
  669. /*****************************************************************************************
  670. *
  671. * Formatting helpers
  672. *
  673. * ***************************************************************************************/
  674. wxString PCB_NET_INSPECTOR_PANEL::formatNetCode( const NETINFO_ITEM* aNet ) const
  675. {
  676. return wxString::Format( wxT( "%.3d" ), aNet->GetNetCode() );
  677. }
  678. wxString PCB_NET_INSPECTOR_PANEL::formatNetName( const NETINFO_ITEM* aNet ) const
  679. {
  680. return UnescapeString( aNet->GetNetname() );
  681. }
  682. wxString PCB_NET_INSPECTOR_PANEL::formatCount( unsigned int aValue ) const
  683. {
  684. return wxString::Format( wxT( "%u" ), aValue );
  685. }
  686. wxString PCB_NET_INSPECTOR_PANEL::formatLength( int64_t aValue ) const
  687. {
  688. return m_frame->MessageTextFromValue( static_cast<long long int>( aValue ),
  689. // don't include unit label in the string when reporting
  690. !m_in_reporting);
  691. }
  692. void PCB_NET_INSPECTOR_PANEL::updateDisplayedRowValues( const std::optional<LIST_ITEM_ITER>& aRow )
  693. {
  694. if( !aRow )
  695. return;
  696. wxDataViewItemArray sel;
  697. m_netsList->GetSelections( sel );
  698. m_data_model->updateItem( aRow );
  699. if( !sel.IsEmpty() )
  700. {
  701. m_netsList->SetSelections( sel );
  702. m_netsList->EnsureVisible( sel.Item( 0 ) );
  703. }
  704. }
  705. /*****************************************************************************************
  706. *
  707. * BOARD_LISTENER event handling
  708. *
  709. * ***************************************************************************************/
  710. void PCB_NET_INSPECTOR_PANEL::OnBoardChanged()
  711. {
  712. m_brd = m_frame->GetBoard();
  713. if( m_brd )
  714. m_brd->AddListener( this );
  715. m_board_loaded = true;
  716. m_board_loading = true;
  717. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  718. auto& cfg = localSettings.m_NetInspectorPanel;
  719. m_searchCtrl->SetValue( cfg.filter_text );
  720. buildNetsList( true );
  721. m_board_loading = false;
  722. }
  723. void PCB_NET_INSPECTOR_PANEL::OnBoardItemAdded( BOARD& aBoard, BOARD_ITEM* aBoardItem )
  724. {
  725. if( !IsShownOnScreen() )
  726. return;
  727. if( NETINFO_ITEM* net = dynamic_cast<NETINFO_ITEM*>( aBoardItem ) )
  728. {
  729. // a new net has been added to the board. add it to our list if it
  730. // passes the netname filter test.
  731. if( netFilterMatches( net ) )
  732. {
  733. std::unique_ptr<LIST_ITEM> new_item = std::make_unique<LIST_ITEM>( net );
  734. // the new net could have some pads already assigned, count them.
  735. new_item->SetPadCount( m_brd->GetNodesCount( net->GetNetCode() ) );
  736. new_item->SetLayerCount( m_brd->GetCopperLayerCount() );
  737. m_data_model->addItem( std::move( new_item ) );
  738. }
  739. }
  740. else if( BOARD_CONNECTED_ITEM* i = dynamic_cast<BOARD_CONNECTED_ITEM*>( aBoardItem ) )
  741. {
  742. std::optional<LIST_ITEM_ITER> r = m_data_model->findItem( i->GetNet() );
  743. if( r )
  744. {
  745. // try to handle frequent operations quickly.
  746. if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( i ) )
  747. {
  748. const std::unique_ptr<LIST_ITEM>& list_item = *r.value();
  749. int len = track->GetLength();
  750. list_item->AddLayerWireLength( len, track->GetLayer() );
  751. if( track->Type() == PCB_VIA_T )
  752. {
  753. list_item->AddViaCount( 1 );
  754. list_item->AddViaLength( calculateViaLength( track ) );
  755. }
  756. updateDisplayedRowValues( r );
  757. return;
  758. }
  759. }
  760. // resort to generic slower net update otherwise.
  761. updateNet( i->GetNet() );
  762. }
  763. else if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( aBoardItem ) )
  764. {
  765. for( const PAD* pad : footprint->Pads() )
  766. {
  767. std::optional<LIST_ITEM_ITER> r = m_data_model->findItem( pad->GetNet() );
  768. if( !r )
  769. {
  770. // if show-zero-pads is off, we might not have this net
  771. // in our list yet, so add it first.
  772. // notice that at this point we are very certain that this net
  773. // will have at least one pad.
  774. if( netFilterMatches( pad->GetNet() ) )
  775. r = m_data_model->addItem( std::make_unique<LIST_ITEM>( pad->GetNet() ) );
  776. }
  777. if( r )
  778. {
  779. const std::unique_ptr<LIST_ITEM>& list_item = *r.value();
  780. int len = pad->GetPadToDieLength();
  781. list_item->AddPadCount( 1 );
  782. list_item->AddPadDieLength( len );
  783. list_item->SetLayerCount( m_brd->GetCopperLayerCount() );
  784. if( list_item->GetPadCount() == 0 && !m_show_zero_pad_nets )
  785. m_data_model->deleteItem( r );
  786. else
  787. updateDisplayedRowValues( r );
  788. }
  789. }
  790. }
  791. }
  792. void PCB_NET_INSPECTOR_PANEL::OnBoardItemsAdded( BOARD& aBoard,
  793. std::vector<BOARD_ITEM*>& aBoardItems )
  794. {
  795. if( !IsShownOnScreen() )
  796. return;
  797. // Rebuild full netlist for large changes
  798. if( aBoardItems.size() > 25 )
  799. {
  800. buildNetsList();
  801. m_netsList->Refresh();
  802. }
  803. else
  804. {
  805. for( BOARD_ITEM* item : aBoardItems )
  806. {
  807. OnBoardItemAdded( aBoard, item );
  808. }
  809. }
  810. }
  811. void PCB_NET_INSPECTOR_PANEL::OnBoardItemRemoved( BOARD& aBoard, BOARD_ITEM* aBoardItem )
  812. {
  813. if( !IsShownOnScreen() )
  814. return;
  815. if( NETINFO_ITEM* net = dynamic_cast<NETINFO_ITEM*>( aBoardItem ) )
  816. {
  817. m_data_model->deleteItem( m_data_model->findItem( net ) );
  818. }
  819. else if( FOOTPRINT* footprint = dynamic_cast<FOOTPRINT*>( aBoardItem ) )
  820. {
  821. for( const PAD* pad : footprint->Pads() )
  822. {
  823. std::optional<LIST_ITEM_ITER> r = m_data_model->findItem( pad->GetNet() );
  824. if( r )
  825. {
  826. const std::unique_ptr<LIST_ITEM>& list_item = *r.value();
  827. int len = pad->GetPadToDieLength();
  828. list_item->SubPadCount( 1 );
  829. list_item->SubPadDieLength( len );
  830. if( list_item->GetPadCount() == 0 && !m_show_zero_pad_nets )
  831. m_data_model->deleteItem( r );
  832. else
  833. updateDisplayedRowValues( r );
  834. }
  835. }
  836. }
  837. else if( BOARD_CONNECTED_ITEM* i = dynamic_cast<BOARD_CONNECTED_ITEM*>( aBoardItem ) )
  838. {
  839. std::optional<LIST_ITEM_ITER> r = m_data_model->findItem( i->GetNet() );
  840. if( r )
  841. {
  842. // try to handle frequent operations quickly.
  843. if( PCB_TRACK* track = dynamic_cast<PCB_TRACK*>( i ) )
  844. {
  845. const std::unique_ptr<LIST_ITEM>& list_item = *r.value();
  846. int len = track->GetLength();
  847. list_item->SubLayerWireLength( len, track->GetLayer() );
  848. if( track->Type() == PCB_VIA_T )
  849. {
  850. list_item->SubViaCount( 1 );
  851. list_item->SubViaLength( calculateViaLength( track ) );
  852. }
  853. updateDisplayedRowValues( r );
  854. return;
  855. }
  856. // resort to generic slower net update otherwise.
  857. updateNet( i->GetNet() );
  858. }
  859. }
  860. }
  861. void PCB_NET_INSPECTOR_PANEL::OnBoardItemsRemoved( BOARD& aBoard,
  862. std::vector<BOARD_ITEM*>& aBoardItems )
  863. {
  864. if( !IsShownOnScreen() )
  865. return;
  866. if( aBoardItems.size() > 25 )
  867. {
  868. buildNetsList();
  869. m_netsList->Refresh();
  870. }
  871. else
  872. {
  873. for( BOARD_ITEM* item : aBoardItems )
  874. {
  875. OnBoardItemRemoved( aBoard, item );
  876. }
  877. }
  878. }
  879. void PCB_NET_INSPECTOR_PANEL::OnBoardNetSettingsChanged( BOARD& aBoard )
  880. {
  881. if( !IsShownOnScreen() )
  882. return;
  883. buildNetsList();
  884. m_netsList->Refresh();
  885. }
  886. void PCB_NET_INSPECTOR_PANEL::OnBoardItemChanged( BOARD& aBoard, BOARD_ITEM* aBoardItem )
  887. {
  888. if( !IsShownOnScreen() )
  889. return;
  890. if( dynamic_cast<BOARD_CONNECTED_ITEM*>( aBoardItem ) != nullptr
  891. || dynamic_cast<FOOTPRINT*>( aBoardItem ) != nullptr )
  892. {
  893. buildNetsList();
  894. m_netsList->Refresh();
  895. }
  896. }
  897. void PCB_NET_INSPECTOR_PANEL::OnBoardItemsChanged( BOARD& aBoard,
  898. std::vector<BOARD_ITEM*>& aBoardItems )
  899. {
  900. if( !IsShownOnScreen() )
  901. return;
  902. buildNetsList();
  903. m_netsList->Refresh();
  904. }
  905. void PCB_NET_INSPECTOR_PANEL::OnBoardCompositeUpdate( BOARD& aBoard,
  906. std::vector<BOARD_ITEM*>& aAddedItems,
  907. std::vector<BOARD_ITEM*>& aRemovedItems,
  908. std::vector<BOARD_ITEM*>& aDeletedItems )
  909. {
  910. if( !IsShownOnScreen() )
  911. return;
  912. buildNetsList();
  913. m_netsList->Refresh();
  914. }
  915. void PCB_NET_INSPECTOR_PANEL::OnBoardHighlightNetChanged( BOARD& aBoard )
  916. {
  917. if( m_highlighting_nets || !IsShownOnScreen() )
  918. return;
  919. if( !m_brd->IsHighLightNetON() )
  920. {
  921. m_netsList->UnselectAll();
  922. }
  923. else
  924. {
  925. const std::set<int>& selected_codes = m_brd->GetHighLightNetCodes();
  926. wxDataViewItemArray new_selection;
  927. new_selection.Alloc( selected_codes.size() );
  928. for( int code : selected_codes )
  929. {
  930. if( std::optional<LIST_ITEM_ITER> r = m_data_model->findItem( code ) )
  931. new_selection.Add( wxDataViewItem( &***r ) );
  932. }
  933. m_netsList->SetSelections( new_selection );
  934. if( !new_selection.IsEmpty() )
  935. m_netsList->EnsureVisible( new_selection.Item( 0 ) );
  936. }
  937. }
  938. /*****************************************************************************************
  939. *
  940. * UI-generated event handling
  941. *
  942. * ***************************************************************************************/
  943. void PCB_NET_INSPECTOR_PANEL::OnShowPanel()
  944. {
  945. buildNetsList();
  946. OnBoardHighlightNetChanged( *m_brd );
  947. }
  948. void PCB_NET_INSPECTOR_PANEL::OnNetsListContextMenu( wxDataViewEvent& event )
  949. {
  950. bool multipleSelections = false;
  951. const LIST_ITEM* selItem = nullptr;
  952. if( m_netsList->GetSelectedItemsCount() == 1 )
  953. {
  954. selItem = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  955. }
  956. else
  957. {
  958. if( m_netsList->GetSelectedItemsCount() > 1 )
  959. multipleSelections = true;
  960. }
  961. wxMenu menu;
  962. // Net edit menu items
  963. wxMenuItem* highlightNet = new wxMenuItem( &menu, ID_HIGHLIGHT_SELECTED_NETS,
  964. _( "Highlight Selected Net" ),
  965. wxEmptyString, wxITEM_NORMAL );
  966. menu.Append( highlightNet );
  967. wxMenuItem* clearHighlighting = new wxMenuItem( &menu, ID_CLEAR_HIGHLIGHTING,
  968. _( "Clear Net Highlighting" ),
  969. wxEmptyString, wxITEM_NORMAL );
  970. menu.Append( clearHighlighting );
  971. RENDER_SETTINGS* renderSettings = m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings();
  972. const std::set<int>& selected_codes = renderSettings->GetHighlightNetCodes();
  973. if( selected_codes.size() == 0 )
  974. clearHighlighting->Enable( false );
  975. menu.AppendSeparator();
  976. wxMenuItem* renameNet = new wxMenuItem( &menu, ID_RENAME_NET, _( "Rename Selected Net" ),
  977. wxEmptyString, wxITEM_NORMAL );
  978. menu.Append( renameNet );
  979. wxMenuItem* deleteNet = new wxMenuItem( &menu, ID_DELETE_NET, _( "Delete Selected Net" ),
  980. wxEmptyString, wxITEM_NORMAL );
  981. menu.Append( deleteNet );
  982. menu.AppendSeparator();
  983. wxMenuItem* addNet = new wxMenuItem( &menu, ID_ADD_NET, _( "Add Net" ),
  984. wxEmptyString, wxITEM_NORMAL );
  985. menu.Append( addNet );
  986. if( !selItem && !multipleSelections )
  987. {
  988. highlightNet->Enable( false );
  989. deleteNet->Enable( false );
  990. renameNet->Enable( false );
  991. }
  992. else
  993. {
  994. if( multipleSelections || selItem->GetIsGroup() )
  995. {
  996. highlightNet->SetItemLabel( _( "Highlight Selected Nets" ) );
  997. renameNet->Enable( false );
  998. deleteNet->SetItemLabel( _( "Delete Selected Nets" ) );
  999. }
  1000. }
  1001. menu.AppendSeparator();
  1002. wxMenuItem* removeSelectedGroup = new wxMenuItem( &menu, ID_REMOVE_SELECTED_GROUP,
  1003. _( "Remove Selected Custom Group" ),
  1004. wxEmptyString, wxITEM_NORMAL );
  1005. menu.Append( removeSelectedGroup );
  1006. if( !selItem || !selItem->GetIsGroup() )
  1007. removeSelectedGroup->Enable( false );
  1008. menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &PCB_NET_INSPECTOR_PANEL::onSettingsMenu, this );
  1009. PopupMenu( &menu );
  1010. }
  1011. void PCB_NET_INSPECTOR_PANEL::OnSearchTextChanged( wxCommandEvent& event )
  1012. {
  1013. SaveSettings();
  1014. buildNetsList();
  1015. }
  1016. void PCB_NET_INSPECTOR_PANEL::onAddGroup()
  1017. {
  1018. wxString newGroupName;
  1019. NETNAME_VALIDATOR validator( &newGroupName );
  1020. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Group name / pattern:" ), _( "New Group" ), newGroupName );
  1021. wxStaticText* help = new wxStaticText( &dlg, wxID_ANY,
  1022. _( "(Use /.../ to indicate a regular expression.)" ) );
  1023. help->SetFont( KIUI::GetInfoFont( this ).Italic() );
  1024. dlg.m_ContentSizer->Add( help, 0, wxALL|wxEXPAND, 5 );
  1025. dlg.SetTextValidator( validator );
  1026. dlg.GetSizer()->SetSizeHints( &dlg );
  1027. if( dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty() )
  1028. return; //Aborted by user
  1029. newGroupName = UnescapeString( dlg.GetValue() );
  1030. if( newGroupName == "" )
  1031. return;
  1032. if( std::find_if( m_custom_group_rules.begin(), m_custom_group_rules.end(),
  1033. [&]( std::unique_ptr<EDA_COMBINED_MATCHER>& rule )
  1034. {
  1035. return rule->GetPattern().Upper() == newGroupName.Upper();
  1036. } ) == m_custom_group_rules.end() )
  1037. {
  1038. m_custom_group_rules.push_back( std::make_unique<EDA_COMBINED_MATCHER>( newGroupName,
  1039. CTX_NET ) );
  1040. SaveSettings();
  1041. }
  1042. buildNetsList();
  1043. }
  1044. void PCB_NET_INSPECTOR_PANEL::onClearHighlighting()
  1045. {
  1046. m_highlighting_nets = true;
  1047. m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings()->SetHighlight( false );
  1048. m_frame->GetCanvas()->GetView()->UpdateAllLayersColor();
  1049. m_frame->GetCanvas()->Refresh();
  1050. m_highlighting_nets = false;
  1051. }
  1052. void PCB_NET_INSPECTOR_PANEL::OnExpandCollapseRow( wxCommandEvent& event )
  1053. {
  1054. if( !m_row_expanding )
  1055. SaveSettings();
  1056. }
  1057. void PCB_NET_INSPECTOR_PANEL::OnHeaderContextMenu( wxCommandEvent& event )
  1058. {
  1059. wxMenu menu;
  1060. generateShowHideColumnMenu( &menu );
  1061. menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &PCB_NET_INSPECTOR_PANEL::onSettingsMenu, this );
  1062. PopupMenu( &menu );
  1063. }
  1064. void PCB_NET_INSPECTOR_PANEL::OnConfigButton( wxCommandEvent& event )
  1065. {
  1066. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  1067. auto& cfg = localSettings.m_NetInspectorPanel;
  1068. const LIST_ITEM* selItem = nullptr;
  1069. if( m_netsList->GetSelectedItemsCount() == 1 )
  1070. {
  1071. selItem = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  1072. }
  1073. wxMenu menu;
  1074. // Filtering menu items
  1075. wxMenuItem* filterByNetName = new wxMenuItem( &menu, ID_FILTER_BY_NET_NAME,
  1076. _( "Filter by Net Name" ),
  1077. wxEmptyString, wxITEM_CHECK );
  1078. menu.Append( filterByNetName );
  1079. filterByNetName->Check( cfg.filter_by_net_name );
  1080. wxMenuItem* filterByNetclass = new wxMenuItem( &menu, ID_FILTER_BY_NETCLASS,
  1081. _( "Filter by Netclass" ),
  1082. wxEmptyString, wxITEM_CHECK );
  1083. menu.Append( filterByNetclass );
  1084. filterByNetclass->Check( cfg.filter_by_netclass );
  1085. menu.AppendSeparator();
  1086. // Grouping menu items
  1087. //wxMenuItem* groupConstraint =
  1088. // new wxMenuItem( &menu, ID_GROUP_BY_CONSTRAINT, _( "Group by DRC Constraint" ),
  1089. // wxEmptyString, wxITEM_CHECK );
  1090. //groupConstraint->Check( m_group_by_constraint );
  1091. //menu.Append( groupConstraint );
  1092. wxMenuItem* groupNetclass = new wxMenuItem( &menu, ID_GROUP_BY_NETCLASS,
  1093. _( "Group by Netclass" ),
  1094. wxEmptyString, wxITEM_CHECK );
  1095. menu.Append( groupNetclass );
  1096. groupNetclass->Check( m_group_by_netclass );
  1097. menu.AppendSeparator();
  1098. wxMenuItem* addGroup = new wxMenuItem( &menu, ID_ADD_GROUP, _( "Add Custom Group" ),
  1099. wxEmptyString, wxITEM_NORMAL );
  1100. menu.Append( addGroup );
  1101. wxMenuItem* removeSelectedGroup = new wxMenuItem( &menu, ID_REMOVE_SELECTED_GROUP,
  1102. _( "Remove Selected Custom Group" ),
  1103. wxEmptyString, wxITEM_NORMAL );
  1104. menu.Append( removeSelectedGroup );
  1105. if( !selItem || !selItem->GetIsGroup() )
  1106. removeSelectedGroup->Enable( false );
  1107. wxMenuItem* removeCustomGroups = new wxMenuItem( &menu, ID_REMOVE_GROUPS,
  1108. _( "Remove All Custom Groups" ),
  1109. wxEmptyString, wxITEM_NORMAL );
  1110. menu.Append( removeCustomGroups );
  1111. removeCustomGroups->Enable( m_custom_group_rules.size() != 0 );
  1112. menu.AppendSeparator();
  1113. wxMenuItem* showZeroNetPads = new wxMenuItem( &menu, ID_SHOW_ZERO_NET_PADS,
  1114. _( "Show Zero Pad Nets" ),
  1115. wxEmptyString, wxITEM_CHECK );
  1116. menu.Append( showZeroNetPads );
  1117. showZeroNetPads->Check( m_show_zero_pad_nets );
  1118. wxMenuItem* showUnconnectedNets = new wxMenuItem( &menu, ID_SHOW_UNCONNECTED_NETS,
  1119. _( "Show Unconnected Nets" ),
  1120. wxEmptyString, wxITEM_CHECK );
  1121. menu.Append( showUnconnectedNets );
  1122. showUnconnectedNets->Check( m_show_unconnected_nets );
  1123. menu.AppendSeparator();
  1124. // Report generation
  1125. wxMenuItem* generateReport = new wxMenuItem( &menu, ID_GENERATE_REPORT,
  1126. _( "Save Net Inspector Report" ),
  1127. wxEmptyString, wxITEM_NORMAL );
  1128. menu.Append( generateReport );
  1129. menu.AppendSeparator();
  1130. // Show / hide columns menu items
  1131. wxMenu* colsMenu = new wxMenu();
  1132. generateShowHideColumnMenu( colsMenu );
  1133. menu.AppendSubMenu( colsMenu, _( "Show / Hide Columns" ) );
  1134. menu.Bind( wxEVT_COMMAND_MENU_SELECTED, &PCB_NET_INSPECTOR_PANEL::onSettingsMenu, this );
  1135. PopupMenu( &menu );
  1136. }
  1137. void PCB_NET_INSPECTOR_PANEL::generateShowHideColumnMenu( wxMenu* target )
  1138. {
  1139. for( int i = 1; i <= COLUMN_LAST_STATIC_COL; ++i )
  1140. {
  1141. wxMenuItem* opt = new wxMenuItem( target, ID_HIDE_COLUMN + i, m_columns[i].display_name,
  1142. wxEmptyString, wxITEM_CHECK );
  1143. wxDataViewColumn* col = getDisplayedColumnForModelField( i );
  1144. target->Append( opt );
  1145. opt->Check( !col->IsHidden() );
  1146. }
  1147. target->AppendSeparator();
  1148. for( std::size_t i = COLUMN_LAST_STATIC_COL + 1; i < m_columns.size(); ++i )
  1149. {
  1150. wxMenuItem* opt = new wxMenuItem( target, ID_HIDE_COLUMN + i, m_columns[i].display_name,
  1151. wxEmptyString, wxITEM_CHECK );
  1152. wxDataViewColumn* col = getDisplayedColumnForModelField( i );
  1153. target->Append( opt );
  1154. opt->Check( !col->IsHidden() );
  1155. }
  1156. }
  1157. void PCB_NET_INSPECTOR_PANEL::onSettingsMenu( wxCommandEvent& event )
  1158. {
  1159. bool saveAndRebuild = true;
  1160. switch( event.GetId() )
  1161. {
  1162. case ID_ADD_NET:
  1163. onAddNet();
  1164. break;
  1165. case ID_RENAME_NET:
  1166. onRenameSelectedNet();
  1167. break;
  1168. case ID_DELETE_NET:
  1169. onDeleteSelectedNet();
  1170. break;
  1171. case ID_ADD_GROUP:
  1172. onAddGroup();
  1173. break;
  1174. case ID_GROUP_BY_CONSTRAINT:
  1175. m_group_by_constraint = !m_group_by_constraint;
  1176. break;
  1177. case ID_GROUP_BY_NETCLASS:
  1178. m_group_by_netclass = !m_group_by_netclass;
  1179. break;
  1180. case ID_FILTER_BY_NET_NAME:
  1181. m_filter_by_net_name = !m_filter_by_net_name;
  1182. break;
  1183. case ID_FILTER_BY_NETCLASS:
  1184. m_filter_by_netclass = !m_filter_by_netclass;
  1185. break;
  1186. case ID_REMOVE_SELECTED_GROUP:
  1187. onRemoveSelectedGroup();
  1188. break;
  1189. case ID_REMOVE_GROUPS:
  1190. m_custom_group_rules.clear();
  1191. break;
  1192. case ID_SHOW_ZERO_NET_PADS:
  1193. m_show_zero_pad_nets = !m_show_zero_pad_nets;
  1194. break;
  1195. case ID_SHOW_UNCONNECTED_NETS:
  1196. m_show_unconnected_nets = !m_show_unconnected_nets;
  1197. break;
  1198. case ID_GENERATE_REPORT:
  1199. generateReport();
  1200. saveAndRebuild = false;
  1201. break;
  1202. case ID_HIGHLIGHT_SELECTED_NETS:
  1203. highlightSelectedNets();
  1204. saveAndRebuild = false;
  1205. break;
  1206. case ID_CLEAR_HIGHLIGHTING:
  1207. onClearHighlighting();
  1208. saveAndRebuild = false;
  1209. break;
  1210. default:
  1211. if( event.GetId() >= ID_HIDE_COLUMN )
  1212. {
  1213. const int columnId = event.GetId() - ID_HIDE_COLUMN;
  1214. wxDataViewColumn* col = getDisplayedColumnForModelField( columnId );
  1215. // Make sure we end up with something non-zero so we can resize it
  1216. col->SetWidth( std::max( col->GetWidth(), 10 ) );
  1217. col->SetHidden( !col->IsHidden() );
  1218. }
  1219. break;
  1220. }
  1221. if( saveAndRebuild )
  1222. {
  1223. SaveSettings();
  1224. buildNetsList();
  1225. }
  1226. }
  1227. void PCB_NET_INSPECTOR_PANEL::onRemoveSelectedGroup()
  1228. {
  1229. if( m_netsList->GetSelectedItemsCount() == 1 )
  1230. {
  1231. auto* selItem = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  1232. if( selItem->GetIsGroup() )
  1233. {
  1234. wxString groupName = selItem->GetGroupName();
  1235. auto groupIter = std::find_if( m_custom_group_rules.begin(), m_custom_group_rules.end(),
  1236. [&]( std::unique_ptr<EDA_COMBINED_MATCHER>& rule )
  1237. {
  1238. return rule->GetPattern() == groupName;
  1239. } );
  1240. if( groupIter != m_custom_group_rules.end() )
  1241. {
  1242. m_custom_group_rules.erase( groupIter );
  1243. SaveSettings();
  1244. buildNetsList();
  1245. }
  1246. }
  1247. }
  1248. }
  1249. void PCB_NET_INSPECTOR_PANEL::generateReport()
  1250. {
  1251. wxFileDialog dlg( this, _( "Save Net Inspector Report File" ), "", "",
  1252. _( "Report file" ) + AddFileExtListToFilter( { "csv" } ),
  1253. wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
  1254. if( dlg.ShowModal() == wxID_CANCEL )
  1255. return;
  1256. wxTextFile f( dlg.GetPath() );
  1257. f.Create();
  1258. wxString txt;
  1259. m_in_reporting = true;
  1260. // Print Header:
  1261. for( auto&& col : m_columns )
  1262. {
  1263. txt += '"';
  1264. if( col.has_units )
  1265. {
  1266. txt += wxString::Format( _( "%s (%s)" ),
  1267. col.csv_name,
  1268. EDA_UNIT_UTILS::GetLabel( m_frame->GetUserUnits() ) );
  1269. }
  1270. else
  1271. {
  1272. txt += col.csv_name;
  1273. }
  1274. txt += wxT( "\";" );
  1275. }
  1276. f.AddLine( txt );
  1277. // Print list of nets:
  1278. const unsigned int num_rows = m_data_model->itemCount();
  1279. for( unsigned int row = 0; row < num_rows; row++ )
  1280. {
  1281. auto& i = m_data_model->itemAt( row );
  1282. if( i.GetIsGroup() || i.GetNetCode() == 0 )
  1283. continue;
  1284. txt = "";
  1285. for( auto&& col : m_columns )
  1286. {
  1287. if( static_cast<int>( col.csv_flags ) & static_cast<int>( CSV_COLUMN_DESC::CSV_QUOTE ) )
  1288. txt += '"' + m_data_model->valueAt( col.num, row ).GetString() + wxT( "\";" );
  1289. else
  1290. txt += m_data_model->valueAt( col.num, row ).GetString() + ';';
  1291. }
  1292. f.AddLine( txt );
  1293. }
  1294. m_in_reporting = false;
  1295. f.Write();
  1296. f.Close();
  1297. }
  1298. void PCB_NET_INSPECTOR_PANEL::OnNetsListItemActivated( wxDataViewEvent& event )
  1299. {
  1300. highlightSelectedNets();
  1301. }
  1302. void PCB_NET_INSPECTOR_PANEL::highlightSelectedNets()
  1303. {
  1304. // ignore selection changes while the whole list is being rebuilt.
  1305. if( m_in_build_nets_list )
  1306. return;
  1307. m_highlighting_nets = true;
  1308. RENDER_SETTINGS* renderSettings = m_frame->GetCanvas()->GetView()->GetPainter()->GetSettings();
  1309. if( m_netsList->HasSelection() )
  1310. {
  1311. wxDataViewItemArray sel;
  1312. m_netsList->GetSelections( sel );
  1313. renderSettings->SetHighlight( false );
  1314. for( unsigned int i = 0; i < sel.GetCount(); ++i )
  1315. {
  1316. const LIST_ITEM* ii = static_cast<const LIST_ITEM*>( sel.Item( i ).GetID() );
  1317. if( ii->GetIsGroup() )
  1318. {
  1319. for( auto c = ii->ChildrenBegin(), end = ii->ChildrenEnd(); c != end; ++c )
  1320. renderSettings->SetHighlight( true, ( *c )->GetNetCode(), true );
  1321. }
  1322. else
  1323. {
  1324. renderSettings->SetHighlight( true, ii->GetNetCode(), true );
  1325. }
  1326. }
  1327. }
  1328. else
  1329. {
  1330. renderSettings->SetHighlight( false );
  1331. }
  1332. m_frame->GetCanvas()->GetView()->UpdateAllLayersColor();
  1333. m_frame->GetCanvas()->Refresh();
  1334. m_highlighting_nets = false;
  1335. }
  1336. void PCB_NET_INSPECTOR_PANEL::OnColumnSorted( wxDataViewEvent& event )
  1337. {
  1338. if( !m_in_build_nets_list )
  1339. SaveSettings();
  1340. }
  1341. void PCB_NET_INSPECTOR_PANEL::OnParentSetupChanged()
  1342. {
  1343. // Rebuilt the nets list, and force rebuild of columns in case the stackup has chanaged
  1344. buildNetsList( true );
  1345. m_netsList->Refresh();
  1346. }
  1347. void PCB_NET_INSPECTOR_PANEL::onAddNet()
  1348. {
  1349. wxString newNetName;
  1350. NETNAME_VALIDATOR validator( &newNetName );
  1351. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Net name:" ), _( "New Net" ), newNetName );
  1352. dlg.SetTextValidator( validator );
  1353. while( true )
  1354. {
  1355. if( dlg.ShowModal() != wxID_OK || dlg.GetValue().IsEmpty() )
  1356. return; //Aborted by user
  1357. newNetName = dlg.GetValue();
  1358. if( m_brd->FindNet( newNetName ) )
  1359. {
  1360. DisplayError( this,
  1361. wxString::Format( _( "Net name '%s' is already in use." ), newNetName ) );
  1362. newNetName = wxEmptyString;
  1363. }
  1364. else
  1365. {
  1366. break;
  1367. }
  1368. }
  1369. NETINFO_ITEM* newnet = new NETINFO_ITEM( m_brd, dlg.GetValue(), 0 );
  1370. m_brd->Add( newnet );
  1371. // We'll get an OnBoardItemAdded callback from this to update our listbox
  1372. m_frame->OnModify();
  1373. }
  1374. void PCB_NET_INSPECTOR_PANEL::onRenameSelectedNet()
  1375. {
  1376. if( m_netsList->GetSelectedItemsCount() == 1 )
  1377. {
  1378. const LIST_ITEM* sel = static_cast<const LIST_ITEM*>( m_netsList->GetSelection().GetID() );
  1379. if( sel->GetIsGroup() )
  1380. return;
  1381. NETINFO_ITEM* net = sel->GetNet();
  1382. wxString fullNetName = net->GetNetname();
  1383. wxString netPath;
  1384. wxString shortNetName;
  1385. if( fullNetName.Contains( wxT( "/" ) ) )
  1386. {
  1387. netPath = fullNetName.BeforeLast( '/' ) + '/';
  1388. shortNetName = fullNetName.AfterLast( '/' );
  1389. }
  1390. else
  1391. {
  1392. shortNetName = fullNetName;
  1393. }
  1394. wxString unescapedShortName = UnescapeString( shortNetName );
  1395. WX_TEXT_ENTRY_DIALOG dlg( this, _( "Net name:" ), _( "Rename Net" ), unescapedShortName );
  1396. NETNAME_VALIDATOR validator( &unescapedShortName );
  1397. dlg.SetTextValidator( validator );
  1398. while( true )
  1399. {
  1400. if( dlg.ShowModal() != wxID_OK || dlg.GetValue() == unescapedShortName )
  1401. return;
  1402. unescapedShortName = dlg.GetValue();
  1403. if( unescapedShortName.IsEmpty() )
  1404. {
  1405. DisplayError( this, wxString::Format( _( "Net name cannot be empty." ),
  1406. unescapedShortName ) );
  1407. continue;
  1408. }
  1409. shortNetName = EscapeString( unescapedShortName, CTX_NETNAME );
  1410. fullNetName = netPath + shortNetName;
  1411. if( m_brd->FindNet( shortNetName ) || m_brd->FindNet( fullNetName ) )
  1412. {
  1413. DisplayError( this, wxString::Format( _( "Net name '%s' is already in use." ),
  1414. unescapedShortName ) );
  1415. unescapedShortName = wxEmptyString;
  1416. }
  1417. else
  1418. {
  1419. break;
  1420. }
  1421. }
  1422. for( BOARD_CONNECTED_ITEM* boardItem : m_frame->GetBoard()->AllConnectedItems() )
  1423. {
  1424. if( boardItem->GetNet() == net )
  1425. boardItem->SetFlags( CANDIDATE );
  1426. else
  1427. boardItem->ClearFlags( CANDIDATE );
  1428. }
  1429. // the changed name might require re-grouping. remove/re-insert is easier.
  1430. auto removed_item = m_data_model->deleteItem( m_data_model->findItem( net ) );
  1431. m_brd->Remove( net );
  1432. net->SetNetname( fullNetName );
  1433. m_brd->Add( net );
  1434. for( BOARD_CONNECTED_ITEM* boardItem : m_frame->GetBoard()->AllConnectedItems() )
  1435. {
  1436. if( boardItem->GetFlags() & CANDIDATE )
  1437. boardItem->SetNet( net );
  1438. }
  1439. buildNetsList();
  1440. if( std::optional<LIST_ITEM_ITER> r = m_data_model->findItem( net ) )
  1441. m_netsList->Select( wxDataViewItem( r.value()->get() ) );
  1442. m_frame->OnModify();
  1443. // Currently only tracks and pads have netname annotations and need to be redrawn,
  1444. // but zones are likely to follow. Since we don't have a way to ask what is current,
  1445. // just refresh all items.
  1446. m_frame->GetCanvas()->GetView()->UpdateAllItems( KIGFX::REPAINT );
  1447. m_frame->GetCanvas()->Refresh();
  1448. }
  1449. }
  1450. void PCB_NET_INSPECTOR_PANEL::onDeleteSelectedNet()
  1451. {
  1452. if( !m_netsList->HasSelection() )
  1453. return;
  1454. wxDataViewItemArray sel;
  1455. m_netsList->GetSelections( sel );
  1456. auto delete_one = [this]( const LIST_ITEM* i )
  1457. {
  1458. if( i->GetPadCount() == 0
  1459. || IsOK( this, wxString::Format( _( "Net '%s' is in use. Delete anyway?" ),
  1460. i->GetNetName() ) ) )
  1461. {
  1462. // This is a bit hacky, but it will do for now, since this is the only path
  1463. // outside the netlist updater where you can remove a net from a BOARD.
  1464. int removedCode = i->GetNetCode();
  1465. m_frame->GetCanvas()->GetView()->UpdateAllItemsConditionally(
  1466. [removedCode]( KIGFX::VIEW_ITEM* aItem ) -> int
  1467. {
  1468. auto boardItem = dynamic_cast<BOARD_CONNECTED_ITEM*>( aItem );
  1469. if( boardItem && boardItem->GetNetCode() == removedCode )
  1470. return KIGFX::REPAINT;
  1471. EDA_TEXT* text = dynamic_cast<EDA_TEXT*>( aItem );
  1472. if( text && text->HasTextVars() )
  1473. {
  1474. text->ClearRenderCache();
  1475. text->ClearBoundingBoxCache();
  1476. return KIGFX::GEOMETRY | KIGFX::REPAINT;
  1477. }
  1478. return 0;
  1479. } );
  1480. m_brd->Remove( i->GetNet() );
  1481. m_frame->OnModify();
  1482. // We'll get an OnBoardItemRemoved callback from this to update our listbox
  1483. }
  1484. };
  1485. for( unsigned int i = 0; i < sel.GetCount(); ++i )
  1486. {
  1487. const LIST_ITEM* ii = static_cast<const LIST_ITEM*>( sel.Item( i ).GetID() );
  1488. if( ii->GetIsGroup() )
  1489. {
  1490. if( ii->ChildrenCount() != 0
  1491. && IsOK( this, wxString::Format( _( "Delete all nets in group '%s'?" ),
  1492. ii->GetGroupName() ) ) )
  1493. {
  1494. // we can't be iterating the children container and deleting items from
  1495. // it at the same time. thus take a copy of it first.
  1496. std::vector<const LIST_ITEM*> children;
  1497. children.reserve( ii->ChildrenCount() );
  1498. std::copy( ii->ChildrenBegin(), ii->ChildrenEnd(), std::back_inserter( children ) );
  1499. for( const LIST_ITEM* c : children )
  1500. delete_one( c );
  1501. }
  1502. }
  1503. else
  1504. {
  1505. delete_one( ii );
  1506. }
  1507. }
  1508. }
  1509. /*****************************************************************************************
  1510. *
  1511. * Application-generated event handling
  1512. *
  1513. * ***************************************************************************************/
  1514. void PCB_NET_INSPECTOR_PANEL::OnLanguageChangedImpl()
  1515. {
  1516. SaveSettings();
  1517. buildNetsList( true );
  1518. m_data_model->updateAllItems();
  1519. }
  1520. void PCB_NET_INSPECTOR_PANEL::onUnitsChanged( wxCommandEvent& event )
  1521. {
  1522. m_data_model->updateAllItems();
  1523. event.Skip();
  1524. }
  1525. /*****************************************************************************************
  1526. *
  1527. * Settings persistence
  1528. *
  1529. * ***************************************************************************************/
  1530. void PCB_NET_INSPECTOR_PANEL::SaveSettings()
  1531. {
  1532. // Don't save settings if a board has not yet been loaded - events fire while we set up the
  1533. // panel which overwrites the settings we haven't yet loaded
  1534. if( !m_board_loaded || m_board_loading )
  1535. return;
  1536. PROJECT_LOCAL_SETTINGS& localSettings = Pgm().GetSettingsManager().Prj().GetLocalSettings();
  1537. auto& cfg = localSettings.m_NetInspectorPanel;
  1538. // User-defined filters / grouping
  1539. cfg.filter_text = m_searchCtrl->GetValue();
  1540. cfg.filter_by_net_name = m_filter_by_net_name;
  1541. cfg.filter_by_netclass = m_filter_by_netclass;
  1542. cfg.group_by_netclass = m_group_by_netclass;
  1543. cfg.group_by_constraint = m_group_by_constraint;
  1544. cfg.show_zero_pad_nets = m_show_zero_pad_nets;
  1545. cfg.show_unconnected_nets = m_show_unconnected_nets;
  1546. // Grid sorting
  1547. wxDataViewColumn* sortingCol = m_netsList->GetSortingColumn();
  1548. cfg.sorting_column = sortingCol ? static_cast<int>( sortingCol->GetModelColumn() ) : -1;
  1549. cfg.sort_order_asc = sortingCol ? sortingCol->IsSortOrderAscending() : true;
  1550. // Column arrangement / sizes
  1551. cfg.col_order.resize( m_data_model->columnCount() );
  1552. cfg.col_widths.resize( m_data_model->columnCount() );
  1553. cfg.col_hidden.resize( m_data_model->columnCount() );
  1554. for( unsigned int ii = 0; ii < m_data_model->columnCount(); ++ii )
  1555. {
  1556. cfg.col_order[ii] = (int) m_netsList->GetColumn( ii )->GetModelColumn();
  1557. cfg.col_widths[ii] = m_netsList->GetColumn( ii )->GetWidth();
  1558. cfg.col_hidden[ii] = m_netsList->GetColumn( ii )->IsHidden();
  1559. }
  1560. // Expanded rows
  1561. cfg.expanded_rows.clear();
  1562. std::vector<std::pair<wxString, wxDataViewItem>> groupItems =
  1563. m_data_model->getGroupDataViewItems();
  1564. for( std::pair<wxString, wxDataViewItem>& item : groupItems )
  1565. {
  1566. if( m_netsList->IsExpanded( item.second ) )
  1567. cfg.expanded_rows.push_back( item.first );
  1568. }
  1569. // Customer group rules
  1570. cfg.custom_group_rules.clear();
  1571. for( const std::unique_ptr<EDA_COMBINED_MATCHER>& rule : m_custom_group_rules )
  1572. cfg.custom_group_rules.push_back( rule->GetPattern() );
  1573. }