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.

771 lines
26 KiB

  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2019 Alexander Shuklin, jasuramme@gmail.com
  5. * Copyright (C) 2019-2022 KiCad Developers, see AUTHORS.txt for contributors.
  6. *
  7. * This program is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU General Public License
  9. * as published by the Free Software Foundation; either version 2
  10. * of the License, or (at your option) any later version.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU General Public License
  18. * along with this program; if not, you may find one here:
  19. * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  20. * or you may search the http://www.gnu.org website for the version 2 license,
  21. * or you may write to the Free Software Foundation, Inc.,
  22. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
  23. */
  24. #include "dialog_board_statistics.h"
  25. #include <kiplatform/ui.h>
  26. #include <confirm.h>
  27. #include <pad.h>
  28. #include <macros.h>
  29. #include <wildcards_and_files_ext.h>
  30. #include <widgets/wx_grid.h>
  31. #include <wx/filedlg.h>
  32. #define COL_LABEL 0
  33. #define COL_AMOUNT 1
  34. // Defines for components view
  35. #define ROW_LABEL 0
  36. #define COL_FRONT_SIDE 1
  37. #define COL_BOTTOM_SIDE 2
  38. #define COL_TOTAL 3
  39. // Defines for board view
  40. #define ROW_BOARD_WIDTH 0
  41. #define ROW_BOARD_HEIGHT 1
  42. #define ROW_BOARD_AREA 2
  43. /**
  44. * The dialog last saved state.
  45. */
  46. struct DIALOG_BOARD_STATISTICS_SAVED_STATE
  47. {
  48. DIALOG_BOARD_STATISTICS_SAVED_STATE() :
  49. excludeNoPins( false ),
  50. subtractHoles( false ),
  51. saveReportInitialized(false)
  52. {
  53. }
  54. // Flags to remember last checkboxes state
  55. bool excludeNoPins;
  56. bool subtractHoles;
  57. // Variables to save last report file name and folder
  58. bool saveReportInitialized; // true after the 3 next string are initialized
  59. wxString saveReportFolder; // last report folder
  60. wxString saveReportName; // last report filename
  61. wxString m_project; // name of the project used to create the last report
  62. // used to reinit last state after a project change
  63. };
  64. static DIALOG_BOARD_STATISTICS_SAVED_STATE s_savedDialogState;
  65. DIALOG_BOARD_STATISTICS::DIALOG_BOARD_STATISTICS( PCB_EDIT_FRAME* aParentFrame ) :
  66. DIALOG_BOARD_STATISTICS_BASE( aParentFrame ),
  67. m_parentFrame(aParentFrame),
  68. m_boardWidth( 0 ),
  69. m_boardHeight( 0 ),
  70. m_boardArea( 0.0 ),
  71. m_hasOutline( false ),
  72. m_startLayerColInitialSize( 1 ),
  73. m_stopLayerColInitialSize( 1 )
  74. {
  75. m_gridDrills->Connect( wxEVT_GRID_COL_SORT,
  76. wxGridEventHandler( DIALOG_BOARD_STATISTICS::drillGridSort ),
  77. nullptr, this );
  78. m_checkBoxExcludeComponentsNoPins->SetValue( s_savedDialogState.excludeNoPins );
  79. m_checkBoxSubtractHoles->SetValue( s_savedDialogState.subtractHoles );
  80. // Make labels for grids
  81. wxFont headingFont = KIUI::GetStatusFont( this );
  82. m_gridComponents->SetCellValue( ROW_LABEL, COL_FRONT_SIDE, _( "Front Side" ) );
  83. m_gridComponents->SetCellFont( ROW_LABEL, COL_FRONT_SIDE, headingFont );
  84. m_gridComponents->SetCellValue( ROW_LABEL, COL_BOTTOM_SIDE, _( "Back Side" ) );
  85. m_gridComponents->SetCellFont( ROW_LABEL, COL_BOTTOM_SIDE, headingFont );
  86. m_gridComponents->SetCellValue( ROW_LABEL, COL_TOTAL, _( "Total" ) );
  87. m_gridComponents->SetCellFont( ROW_LABEL, COL_TOTAL, headingFont );
  88. m_gridBoard->SetCellValue( 0, 0, _( "Width:" ) );
  89. m_gridBoard->SetCellAlignment( 0, 0, wxALIGN_LEFT, wxALIGN_CENTRE );
  90. m_gridBoard->SetCellValue( 1, 0, _( "Height:" ) );
  91. m_gridBoard->SetCellAlignment( 1, 0, wxALIGN_LEFT, wxALIGN_CENTRE );
  92. m_gridBoard->SetCellValue( 2, 0, _( "Area:" ) );
  93. m_gridBoard->SetCellAlignment( 2, 0, wxALIGN_LEFT, wxALIGN_CENTRE );
  94. wxGrid* grids[] = { m_gridComponents, m_gridPads, m_gridVias, m_gridBoard };
  95. for( auto& grid : grids )
  96. {
  97. // Remove wxgrid's selection boxes
  98. grid->SetCellHighlightPenWidth( 0 );
  99. grid->SetColMinimalAcceptableWidth( 80 );
  100. for( int i = 0; i < grid->GetNumberRows(); i++ )
  101. grid->SetCellAlignment( i, COL_LABEL, wxALIGN_LEFT, wxALIGN_CENTRE );
  102. }
  103. wxFileName fn = m_parentFrame->GetBoard()->GetFileName();
  104. if( !s_savedDialogState.saveReportInitialized
  105. || s_savedDialogState.m_project != Prj().GetProjectFullName() )
  106. {
  107. fn.SetName( fn.GetName() + wxT( "_report" ) );
  108. fn.SetExt( wxT( "txt" ) );
  109. s_savedDialogState.saveReportName = fn.GetFullName();
  110. s_savedDialogState.saveReportFolder = wxPathOnly( Prj().GetProjectFullName() );
  111. s_savedDialogState.m_project = Prj().GetProjectFullName();
  112. s_savedDialogState.saveReportInitialized = true;
  113. }
  114. // The wxStdDialogButtonSizer wxID_CANCLE button is in fact a close button
  115. // Nothing to cancel:
  116. m_sdbControlSizerCancel->SetLabel( _( "Close" ) );
  117. }
  118. void DIALOG_BOARD_STATISTICS::refreshItemsTypes()
  119. {
  120. m_fpTypes.clear();
  121. // If you need some more types to be shown, simply add them to the corresponding list
  122. m_fpTypes.push_back( FP_LINE_ITEM( FP_THROUGH_HOLE, FP_THROUGH_HOLE, _( "THT:" ) ) );
  123. m_fpTypes.push_back( FP_LINE_ITEM( FP_SMD, FP_SMD, _( "SMD:" ) ) );
  124. m_fpTypes.push_back( FP_LINE_ITEM( FP_THROUGH_HOLE|FP_SMD, 0, _( "Unspecified:" ) ) );
  125. m_padTypes.clear();
  126. m_padTypes.push_back( LINE_ITEM<PAD_ATTRIB>( PAD_ATTRIB::PTH, _( "Through hole:" ) ) );
  127. m_padTypes.push_back( LINE_ITEM<PAD_ATTRIB>( PAD_ATTRIB::SMD, _( "SMD:" ) ) );
  128. m_padTypes.push_back( LINE_ITEM<PAD_ATTRIB>( PAD_ATTRIB::CONN, _( "Connector:" ) ) );
  129. m_padTypes.push_back( LINE_ITEM<PAD_ATTRIB>( PAD_ATTRIB::NPTH, _( "NPTH:" ) ) );
  130. m_viaTypes.clear();
  131. m_viaTypes.push_back( LINE_ITEM<VIATYPE>( VIATYPE::THROUGH, _( "Through vias:" ) ) );
  132. m_viaTypes.push_back( LINE_ITEM<VIATYPE>( VIATYPE::BLIND_BURIED, _( "Blind/buried:" ) ) );
  133. m_viaTypes.push_back( LINE_ITEM<VIATYPE>( VIATYPE::MICROVIA, _( "Micro vias:" ) ) );
  134. // If there not enough rows in grids, append some
  135. int appendRows = m_fpTypes.size() + 2 - m_gridComponents->GetNumberRows();
  136. if( appendRows > 0 )
  137. m_gridComponents->AppendRows( appendRows );
  138. appendRows = m_padTypes.size() + 1 - m_gridPads->GetNumberRows();
  139. if( appendRows > 0 )
  140. m_gridPads->AppendRows( appendRows );
  141. appendRows = m_viaTypes.size() + 1 - m_gridVias->GetNumberRows();
  142. if( appendRows )
  143. m_gridVias->AppendRows( appendRows );
  144. }
  145. bool DIALOG_BOARD_STATISTICS::TransferDataToWindow()
  146. {
  147. refreshItemsTypes();
  148. getDataFromPCB();
  149. updateWidets();
  150. Layout();
  151. m_drillsPanel->Layout();
  152. m_gridDrills->AutoSizeColumns();
  153. m_startLayerColInitialSize = m_gridDrills->GetColSize( DRILL_LINE_ITEM::COL_START_LAYER );
  154. m_stopLayerColInitialSize = m_gridDrills->GetColSize( DRILL_LINE_ITEM::COL_STOP_LAYER );
  155. // Add space for the vertical scrollbar, so that it won't overlap with the cells.
  156. m_gridDrills->SetMinSize( wxSize( m_gridDrills->GetEffectiveMinSize().x
  157. + wxSystemSettings::GetMetric( wxSYS_VSCROLL_X ),
  158. 60 ) );
  159. adjustDrillGridColumns();
  160. finishDialogSettings();
  161. return true;
  162. }
  163. void DIALOG_BOARD_STATISTICS::getDataFromPCB()
  164. {
  165. BOARD* board = m_parentFrame->GetBoard();
  166. m_drillTypes.clear();
  167. // Get footprints and pads count
  168. for( FOOTPRINT* footprint : board->Footprints() )
  169. {
  170. // Do not proceed footprints with no pads if checkbox checked
  171. if( m_checkBoxExcludeComponentsNoPins->GetValue() && ! footprint->Pads().size() )
  172. continue;
  173. // Go through components types list
  174. for( FP_LINE_ITEM& line : m_fpTypes )
  175. {
  176. if( ( footprint->GetAttributes() & line.attribute_mask ) == line.attribute_value )
  177. {
  178. if( footprint->IsFlipped() )
  179. line.backSideQty++;
  180. else
  181. line.frontSideQty++;
  182. break;
  183. }
  184. }
  185. for( PAD* pad : footprint->Pads() )
  186. {
  187. // Go through pads types list
  188. for( LINE_ITEM<PAD_ATTRIB>& line : m_padTypes )
  189. {
  190. if( pad->GetAttribute() == line.attribute )
  191. {
  192. line.qty++;
  193. break;
  194. }
  195. }
  196. if( pad->GetDrillSize().x > 0 && pad->GetDrillSize().y > 0 )
  197. {
  198. PCB_LAYER_ID top, bottom;
  199. if( pad->GetLayerSet().CuStack().empty() )
  200. {
  201. // The pad is not on any copper layer
  202. top = UNDEFINED_LAYER;
  203. bottom = UNDEFINED_LAYER;
  204. }
  205. else
  206. {
  207. top = pad->GetLayerSet().CuStack().front();
  208. bottom = pad->GetLayerSet().CuStack().back();
  209. }
  210. DRILL_LINE_ITEM drill( pad->GetDrillSize().x, pad->GetDrillSize().y,
  211. pad->GetDrillShape(),
  212. pad->GetAttribute() != PAD_ATTRIB::NPTH,
  213. true, top, bottom );
  214. auto it = m_drillTypes.begin();
  215. for( ; it != m_drillTypes.end(); ++it )
  216. {
  217. if( *it == drill )
  218. {
  219. it->qty++;
  220. break;
  221. }
  222. }
  223. if( it == m_drillTypes.end() )
  224. {
  225. drill.qty = 1;
  226. m_drillTypes.push_back( drill );
  227. m_gridDrills->InsertRows();
  228. }
  229. }
  230. }
  231. }
  232. // Get via counts
  233. for( PCB_TRACK* track : board->Tracks() )
  234. {
  235. if( PCB_VIA* via = dyn_cast<PCB_VIA*>( track ) )
  236. {
  237. for( LINE_ITEM<VIATYPE>& line : m_viaTypes )
  238. {
  239. if( via->GetViaType() == line.attribute )
  240. {
  241. line.qty++;
  242. break;
  243. }
  244. }
  245. DRILL_LINE_ITEM drill( via->GetDrillValue(), via->GetDrillValue(),
  246. PAD_DRILL_SHAPE_CIRCLE, true, false, via->TopLayer(),
  247. via->BottomLayer() );
  248. auto it = m_drillTypes.begin();
  249. for( ; it != m_drillTypes.end(); ++it )
  250. {
  251. if( *it == drill )
  252. {
  253. it->qty++;
  254. break;
  255. }
  256. }
  257. if( it == m_drillTypes.end() )
  258. {
  259. drill.qty = 1;
  260. m_drillTypes.push_back( drill );
  261. m_gridDrills->InsertRows();
  262. }
  263. }
  264. }
  265. sort( m_drillTypes.begin(), m_drillTypes.end(),
  266. DRILL_LINE_ITEM::COMPARE( DRILL_LINE_ITEM::COL_COUNT, false ) );
  267. bool boundingBoxCreated = false; //flag if bounding box initialized
  268. BOX2I bbox;
  269. SHAPE_POLY_SET polySet;
  270. m_hasOutline = board->GetBoardPolygonOutlines( polySet );
  271. // If board has no Edge Cuts lines, board->GetBoardPolygonOutlines will
  272. // return small rectangle, so we double check that
  273. bool edgeCutsExists = false;
  274. for( BOARD_ITEM* drawing : board->Drawings() )
  275. {
  276. if( drawing->GetLayer() == Edge_Cuts )
  277. {
  278. edgeCutsExists = true;
  279. break;
  280. }
  281. }
  282. if( !edgeCutsExists )
  283. m_hasOutline = false;
  284. if( m_hasOutline )
  285. {
  286. m_boardArea = 0.0;
  287. for( int i = 0; i < polySet.OutlineCount(); i++ )
  288. {
  289. SHAPE_LINE_CHAIN& outline = polySet.Outline( i );
  290. m_boardArea += outline.Area();
  291. // If checkbox "subtract holes" is checked
  292. if( m_checkBoxSubtractHoles->GetValue() )
  293. {
  294. for( int j = 0; j < polySet.HoleCount( i ); j++ )
  295. m_boardArea -= polySet.Hole( i, j ).Area();
  296. }
  297. if( boundingBoxCreated )
  298. {
  299. bbox.Merge( outline.BBox() );
  300. }
  301. else
  302. {
  303. bbox = outline.BBox();
  304. boundingBoxCreated = true;
  305. }
  306. }
  307. m_boardWidth = bbox.GetWidth();
  308. m_boardHeight = bbox.GetHeight();
  309. }
  310. }
  311. static wxString formatCount( int aCount )
  312. {
  313. return wxString::Format( wxT( "%i" ), aCount );
  314. };
  315. void DIALOG_BOARD_STATISTICS::updateWidets()
  316. {
  317. int totalPads = 0;
  318. int row = 0;
  319. for( const LINE_ITEM<PAD_ATTRIB>& line : m_padTypes )
  320. {
  321. m_gridPads->SetCellValue( row, COL_LABEL, line.title );
  322. m_gridPads->SetCellValue( row, COL_AMOUNT, formatCount( line.qty ) );
  323. totalPads += line.qty;
  324. row++;
  325. }
  326. m_gridPads->SetCellValue( row, COL_LABEL, _( "Total:" ) );
  327. m_gridPads->SetCellValue( row, COL_AMOUNT, formatCount( totalPads ) );
  328. int totalVias = 0;
  329. row = 0;
  330. for( const LINE_ITEM<VIATYPE>& line : m_viaTypes )
  331. {
  332. m_gridVias->SetCellValue( row, COL_LABEL, line.title );
  333. m_gridVias->SetCellValue( row, COL_AMOUNT, formatCount( line.qty ) );
  334. totalVias += line.qty;
  335. row++;
  336. }
  337. m_gridVias->SetCellValue( row, COL_LABEL, _( "Total:" ) );
  338. m_gridVias->SetCellValue( row, COL_AMOUNT, formatCount( totalVias ) );
  339. int totalFront = 0;
  340. int totalBack = 0;
  341. // We don't use row 0, as there labels are
  342. row = 1;
  343. for( const FP_LINE_ITEM& line : m_fpTypes )
  344. {
  345. m_gridComponents->SetCellValue( row, COL_LABEL, line.title );
  346. m_gridComponents->SetCellValue( row, COL_FRONT_SIDE, formatCount( line.frontSideQty ) );
  347. m_gridComponents->SetCellValue( row, COL_BOTTOM_SIDE, formatCount( line.backSideQty ) );
  348. m_gridComponents->SetCellValue( row, 3, formatCount( line.frontSideQty + line.backSideQty ) );
  349. totalFront += line.frontSideQty;
  350. totalBack += line.backSideQty;
  351. row++;
  352. }
  353. m_gridComponents->SetCellValue( row, COL_LABEL, _( "Total:" ) );
  354. m_gridComponents->SetCellValue( row, COL_FRONT_SIDE, formatCount( totalFront ) );
  355. m_gridComponents->SetCellValue( row, COL_BOTTOM_SIDE, formatCount( totalBack ) );
  356. m_gridComponents->SetCellValue( row, COL_TOTAL, formatCount( totalFront + totalBack ) );
  357. if( m_hasOutline )
  358. {
  359. m_gridBoard->SetCellValue( ROW_BOARD_WIDTH, COL_AMOUNT,
  360. m_parentFrame->MessageTextFromValue( m_boardWidth ) );
  361. m_gridBoard->SetCellValue( ROW_BOARD_HEIGHT, COL_AMOUNT,
  362. m_parentFrame->MessageTextFromValue( m_boardHeight ) );
  363. m_gridBoard->SetCellValue( ROW_BOARD_AREA, COL_AMOUNT,
  364. m_parentFrame->MessageTextFromValue( m_boardArea, true,
  365. EDA_DATA_TYPE::AREA ) );
  366. }
  367. else
  368. {
  369. m_gridBoard->SetCellValue( ROW_BOARD_WIDTH, COL_AMOUNT, _( "unknown" ) );
  370. m_gridBoard->SetCellValue( ROW_BOARD_HEIGHT, COL_AMOUNT, _( "unknown" ) );
  371. m_gridBoard->SetCellValue( ROW_BOARD_AREA, COL_AMOUNT, _( "unknown" ) );
  372. }
  373. updateDrillGrid();
  374. m_gridComponents->AutoSize();
  375. m_gridPads->AutoSize();
  376. m_gridBoard->AutoSize();
  377. m_gridVias->AutoSize();
  378. adjustDrillGridColumns();
  379. }
  380. void DIALOG_BOARD_STATISTICS::updateDrillGrid()
  381. {
  382. BOARD* board = m_parentFrame->GetBoard();
  383. int row = 0;
  384. for( const DRILL_LINE_ITEM& line : m_drillTypes )
  385. {
  386. wxString shapeStr;
  387. wxString startLayerStr;
  388. wxString stopLayerStr;
  389. switch( line.shape )
  390. {
  391. case PAD_DRILL_SHAPE_CIRCLE: shapeStr = _( "Round" ); break;
  392. case PAD_DRILL_SHAPE_OBLONG: shapeStr = _( "Slot" ); break;
  393. default: shapeStr = _( "???" ); break;
  394. }
  395. if( line.startLayer == UNDEFINED_LAYER )
  396. startLayerStr = _( "N/A" );
  397. else
  398. startLayerStr = board->GetLayerName( line.startLayer );
  399. if( line.stopLayer == UNDEFINED_LAYER )
  400. stopLayerStr = _( "N/A" );
  401. else
  402. stopLayerStr = board->GetLayerName( line.stopLayer );
  403. m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_COUNT, formatCount( line.qty ) );
  404. m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_SHAPE, shapeStr );
  405. m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_X_SIZE,
  406. m_parentFrame->MessageTextFromValue( line.xSize ) );
  407. m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_Y_SIZE,
  408. m_parentFrame->MessageTextFromValue( line.ySize ) );
  409. m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_PLATED,
  410. line.isPlated ? _( "PTH" ) : _( "NPTH" ) );
  411. m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_VIA_PAD,
  412. line.isPad ? _( "Pad" ) : _( "Via" ) );
  413. m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_START_LAYER, startLayerStr );
  414. m_gridDrills->SetCellValue( row, DRILL_LINE_ITEM::COL_STOP_LAYER, stopLayerStr );
  415. row++;
  416. }
  417. }
  418. void DIALOG_BOARD_STATISTICS::printGridToStringAsTable( wxGrid* aGrid, wxString& aStr,
  419. bool aUseColLabels,
  420. bool aUseFirstColAsLabel )
  421. {
  422. std::vector<int> widths( aGrid->GetNumberCols(), 0 );
  423. int rowLabelsWidth = 0;
  424. // Determine column widths.
  425. if( aUseColLabels )
  426. {
  427. for( int col = 0; col < aGrid->GetNumberCols(); col++ )
  428. widths[col] = aGrid->GetColLabelValue( col ).length();
  429. }
  430. for( int row = 0; row < aGrid->GetNumberRows(); row++ )
  431. {
  432. rowLabelsWidth = std::max<int>( rowLabelsWidth, aGrid->GetRowLabelValue( row ).length() );
  433. for( int col = 0; col < aGrid->GetNumberCols(); col++ )
  434. widths[col] = std::max<int>( widths[col], aGrid->GetCellValue( row, col ).length() );
  435. }
  436. // Print the cells.
  437. wxString tmp;
  438. // Print column labels.
  439. aStr << wxT( "|" );
  440. for( int col = 0; col < aGrid->GetNumberCols(); col++ )
  441. {
  442. if( aUseColLabels )
  443. tmp.Printf( wxT( " %*s |" ), widths[col], aGrid->GetColLabelValue( col ) );
  444. else
  445. tmp.Printf( wxT( " %*s |" ), widths[col], aGrid->GetCellValue( 0, col ) );
  446. aStr << tmp;
  447. }
  448. aStr << wxT( "\n" );
  449. // Print column label horizontal separators.
  450. aStr << wxT( "|" );
  451. for( int col = 0; col < aGrid->GetNumberCols(); col++ )
  452. {
  453. aStr << wxT( "-" );
  454. aStr.Append( '-', widths[col] );
  455. aStr << wxT( "-|" );
  456. }
  457. aStr << wxT( "\n" );
  458. // Print regular cells.
  459. int firstRow = 0, firstCol = 0;
  460. if( !aUseColLabels )
  461. firstRow = 1;
  462. if( aUseFirstColAsLabel )
  463. firstCol = 1;
  464. for( int row = firstRow; row < aGrid->GetNumberRows(); row++ )
  465. {
  466. if( aUseFirstColAsLabel )
  467. tmp.Printf( wxT( "|%-*s |" ), widths[0], aGrid->GetCellValue( row, 0 ) );
  468. else
  469. tmp.Printf( wxT( "|" ) );
  470. aStr << tmp;
  471. for( int col = firstCol; col < aGrid->GetNumberCols(); col++ )
  472. {
  473. tmp.Printf( wxT( " %*s |" ), widths[col], aGrid->GetCellValue( row, col ) );
  474. aStr << tmp;
  475. }
  476. aStr << wxT( "\n" );
  477. }
  478. }
  479. void DIALOG_BOARD_STATISTICS::adjustDrillGridColumns()
  480. {
  481. wxGridUpdateLocker deferRepaintsTillLeavingScope( m_gridDrills );
  482. m_gridDrills->EnsureColLabelsVisible();
  483. double remainingWidth = KIPLATFORM::UI::GetUnobscuredSize( m_gridDrills ).x;
  484. // Find the total current width
  485. for( int i = 0; i < m_gridDrills->GetNumberCols(); i++ )
  486. {
  487. if( i != DRILL_LINE_ITEM::COL_START_LAYER && i != DRILL_LINE_ITEM::COL_STOP_LAYER )
  488. remainingWidth -= m_gridDrills->GetColSize( i );
  489. }
  490. double scalingFactor = std::max( 1.0,
  491. remainingWidth
  492. / ( m_startLayerColInitialSize + m_stopLayerColInitialSize ) );
  493. int startLayerColWidth = static_cast<int>( m_startLayerColInitialSize * scalingFactor );
  494. int stopLayerColWidth = static_cast<int>( m_stopLayerColInitialSize * scalingFactor );
  495. m_gridDrills->SetColSize( DRILL_LINE_ITEM::COL_START_LAYER, startLayerColWidth );
  496. m_gridDrills->SetColSize( DRILL_LINE_ITEM::COL_STOP_LAYER, stopLayerColWidth );
  497. }
  498. void DIALOG_BOARD_STATISTICS::checkboxClicked( wxCommandEvent& aEvent )
  499. {
  500. s_savedDialogState.excludeNoPins = m_checkBoxExcludeComponentsNoPins->GetValue();
  501. s_savedDialogState.subtractHoles = m_checkBoxSubtractHoles->GetValue();
  502. refreshItemsTypes();
  503. getDataFromPCB();
  504. updateWidets();
  505. Layout();
  506. m_drillsPanel->Layout();
  507. }
  508. void DIALOG_BOARD_STATISTICS::saveReportClicked( wxCommandEvent& aEvent )
  509. {
  510. FILE* outFile;
  511. wxString msg;
  512. wxString boardName;
  513. wxFileName fn = m_parentFrame->GetBoard()->GetFileName();
  514. boardName = fn.GetName();
  515. wxFileDialog dlg( this, _( "Save Report File" ), s_savedDialogState.saveReportFolder,
  516. s_savedDialogState.saveReportName, TextFileWildcard(),
  517. wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
  518. if( dlg.ShowModal() == wxID_CANCEL )
  519. return;
  520. s_savedDialogState.saveReportFolder = wxPathOnly( dlg.GetPath() );
  521. s_savedDialogState.saveReportName = dlg.GetFilename();
  522. outFile = wxFopen( dlg.GetPath(), wxT( "wt" ) );
  523. if( outFile == nullptr )
  524. {
  525. msg.Printf( _( "Failed to create file '%s'." ), dlg.GetPath() );
  526. DisplayErrorMessage( this, msg );
  527. return;
  528. }
  529. msg << _( "PCB statistics report\n=====================" ) << wxT( "\n" );
  530. msg << wxS( "- " ) << _( "Date" ) << wxS( ": " ) << wxDateTime::Now().Format() << wxT( "\n" );
  531. msg << wxS( "- " ) << _( "Project" ) << wxS( ": " )<< Prj().GetProjectName() << wxT( "\n" );
  532. msg << wxS( "- " ) << _( "Board name" ) << wxS( ": " )<< boardName << wxT( "\n" );
  533. msg << wxT( "\n" );
  534. msg << _( "Board" ) << wxT( "\n-----\n" );
  535. if( m_hasOutline )
  536. {
  537. msg << wxS( "- " ) << _( "Width" ) << wxS( ": " )
  538. << m_parentFrame->MessageTextFromValue( m_boardWidth ) << wxT( "\n" );
  539. msg << wxS( "- " ) << _( "Height" ) << wxS( ": " )
  540. << m_parentFrame->MessageTextFromValue( m_boardHeight ) << wxT( "\n" );
  541. msg << wxS( "- " ) << _( "Area" ) + wxS( ": " )
  542. << m_parentFrame->MessageTextFromValue( m_boardArea, true, EDA_DATA_TYPE::AREA );
  543. msg << wxT( "\n" );
  544. }
  545. else
  546. {
  547. msg << wxS( "- " ) << _( "Width" ) << wxS( ": " ) << _( "unknown" ) << wxT( "\n" );
  548. msg << wxS( "- " ) << _( "Height" ) << wxS( ": " ) << _( "unknown" ) << wxT( "\n" );
  549. msg << wxS( "- " ) << _( "Area" ) << wxS( ": " ) << _( "unknown" ) << wxT( "\n" );
  550. }
  551. msg << wxT( "\n" );
  552. msg << _( "Pads" ) << wxT( "\n----\n" );
  553. for( auto& type : m_padTypes )
  554. msg << wxT( "- " ) << type.title << wxS( " " ) << type.qty << wxT( "\n" );
  555. msg << wxT( "\n" );
  556. msg << _( "Vias" ) << wxT( "\n----\n" );
  557. for( auto& type : m_viaTypes )
  558. msg << wxT( "- " ) << type.title << wxS( " " ) << type.qty << wxT( "\n" );
  559. // We will save data about components in the table.
  560. // We have to calculate column widths
  561. std::vector<int> widths;
  562. std::vector<wxString> labels{ wxT( "" ), _( "Front Side" ), _( "Back Side" ), _( "Total" ) };
  563. wxString tmp;
  564. widths.reserve( labels.size() );
  565. for( const wxString& label : labels )
  566. widths.push_back( label.size() );
  567. int frontTotal = 0;
  568. int backTotal = 0;
  569. for( const FP_LINE_ITEM& line : m_fpTypes )
  570. {
  571. // Get maximum width for left label column
  572. widths[0] = std::max<int>( line.title.size(), widths[0] );
  573. frontTotal += line.frontSideQty;
  574. backTotal += line.backSideQty;
  575. }
  576. // Get maximum width for other columns
  577. tmp.Printf( wxT( "%i" ), frontTotal );
  578. widths[1] = std::max<int>( tmp.size(), widths[1] );
  579. tmp.Printf( wxT( "%i" ), backTotal );
  580. widths[2] = std::max<int>( tmp.size(), widths[2] );
  581. tmp.Printf( wxT( "%i" ), frontTotal + backTotal );
  582. widths[3] = std::max<int>( tmp.size(), widths[3] );
  583. //Write components amount to file
  584. msg << wxT( "\n" );
  585. msg << _( "Components" ) << wxT( "\n----------\n" );
  586. msg << wxT( "\n" );
  587. printGridToStringAsTable( m_gridComponents, msg, false, true );
  588. msg << wxT( "\n" );
  589. msg << _( "Drill holes" ) << wxT( "\n-----------\n" );
  590. msg << wxT( "\n" );
  591. printGridToStringAsTable( m_gridDrills, msg, true, false );
  592. if( fprintf( outFile, "%s", TO_UTF8( msg ) ) < 0 )
  593. {
  594. msg.Printf( _( "Error writing file '%s'." ), dlg.GetPath() );
  595. DisplayErrorMessage( this, msg );
  596. }
  597. fclose( outFile );
  598. }
  599. void DIALOG_BOARD_STATISTICS::drillGridSize( wxSizeEvent& aEvent )
  600. {
  601. aEvent.Skip();
  602. adjustDrillGridColumns();
  603. }
  604. void DIALOG_BOARD_STATISTICS::drillGridSort( wxGridEvent& aEvent )
  605. {
  606. DRILL_LINE_ITEM::COL_ID colId = static_cast<DRILL_LINE_ITEM::COL_ID>( aEvent.GetCol() );
  607. bool ascending = !( m_gridDrills->IsSortingBy( colId )
  608. && m_gridDrills->IsSortOrderAscending() );
  609. sort( m_drillTypes.begin(), m_drillTypes.end(), DRILL_LINE_ITEM::COMPARE( colId, ascending ) );
  610. updateDrillGrid();
  611. }
  612. DIALOG_BOARD_STATISTICS::~DIALOG_BOARD_STATISTICS()
  613. {
  614. }