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.

2412 lines
76 KiB

2 years ago
5 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
6 years ago
2 years ago
2 years ago
3 years ago
2 years ago
2 years ago
2 years ago
5 years ago
2 years ago
5 years ago
7 years ago
2 years ago
2 years ago
2 years ago
2 years ago
4 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
2 years ago
  1. /*
  2. * This program source code file is part of KiCad, a free EDA CAD application.
  3. *
  4. * Copyright (C) 2017 Oliver Walters
  5. * Copyright (C) 2017-2023 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 <common.h>
  25. #include <base_units.h>
  26. #include <bitmaps.h>
  27. #include <symbol_library.h>
  28. #include <confirm.h>
  29. #include <eda_doc.h>
  30. #include <wildcards_and_files_ext.h>
  31. #include <schematic_settings.h>
  32. #include <general.h>
  33. #include <grid_tricks.h>
  34. #include <string_utils.h>
  35. #include <kiface_base.h>
  36. #include <sch_commit.h>
  37. #include <sch_edit_frame.h>
  38. #include <sch_reference_list.h>
  39. #include <schematic.h>
  40. #include <tools/sch_editor_control.h>
  41. #include <kiplatform/ui.h>
  42. #include <widgets/grid_text_button_helpers.h>
  43. #include <widgets/bitmap_button.h>
  44. #include <widgets/std_bitmap_button.h>
  45. #include <widgets/wx_grid.h>
  46. #include <wx/debug.h>
  47. #include <wx/ffile.h>
  48. #include <wx/grid.h>
  49. #include <wx/textdlg.h>
  50. #include <wx/filedlg.h>
  51. #include <wx/msgdlg.h>
  52. #include <dialogs/eda_view_switcher.h>
  53. #include "dialog_symbol_fields_table.h"
  54. #include <fields_data_model.h>
  55. #include <eda_list_dialog.h>
  56. #include <project_sch.h>
  57. wxDEFINE_EVENT( EDA_EVT_CLOSE_DIALOG_SYMBOL_FIELDS_TABLE, wxCommandEvent );
  58. #ifdef __WXMAC__
  59. #define COLUMN_MARGIN 3
  60. #else
  61. #define COLUMN_MARGIN 15
  62. #endif
  63. using SCOPE = FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE;
  64. enum
  65. {
  66. MYID_SELECT_FOOTPRINT = GRIDTRICKS_FIRST_CLIENT_ID,
  67. MYID_SHOW_DATASHEET
  68. };
  69. class FIELDS_EDITOR_GRID_TRICKS : public GRID_TRICKS
  70. {
  71. public:
  72. FIELDS_EDITOR_GRID_TRICKS( DIALOG_SHIM* aParent, WX_GRID* aGrid,
  73. wxDataViewListCtrl* aFieldsCtrl,
  74. FIELDS_EDITOR_GRID_DATA_MODEL* aDataModel ) :
  75. GRID_TRICKS( aGrid ),
  76. m_dlg( aParent ),
  77. m_fieldsCtrl( aFieldsCtrl ),
  78. m_dataModel( aDataModel )
  79. {}
  80. protected:
  81. void showPopupMenu( wxMenu& menu, wxGridEvent& aEvent ) override
  82. {
  83. if( m_grid->GetGridCursorCol() == FOOTPRINT_FIELD )
  84. {
  85. menu.Append( MYID_SELECT_FOOTPRINT, _( "Select Footprint..." ),
  86. _( "Browse for footprint" ) );
  87. menu.AppendSeparator();
  88. }
  89. else if( m_grid->GetGridCursorCol() == DATASHEET_FIELD )
  90. {
  91. menu.Append( MYID_SHOW_DATASHEET, _( "Show Datasheet" ),
  92. _( "Show datasheet in browser" ) );
  93. menu.AppendSeparator();
  94. }
  95. GRID_TRICKS::showPopupMenu( menu, aEvent );
  96. }
  97. void doPopupSelection( wxCommandEvent& event ) override
  98. {
  99. if( event.GetId() == MYID_SELECT_FOOTPRINT )
  100. {
  101. // pick a footprint using the footprint picker.
  102. wxString fpid = m_grid->GetCellValue( m_grid->GetGridCursorRow(), FOOTPRINT_FIELD );
  103. if( KIWAY_PLAYER* frame = m_dlg->Kiway().Player( FRAME_FOOTPRINT_CHOOSER, true, m_dlg ) )
  104. {
  105. if( frame->ShowModal( &fpid, m_dlg ) )
  106. m_grid->SetCellValue( m_grid->GetGridCursorRow(), FOOTPRINT_FIELD, fpid );
  107. frame->Destroy();
  108. }
  109. }
  110. else if (event.GetId() == MYID_SHOW_DATASHEET )
  111. {
  112. wxString datasheet_uri = m_grid->GetCellValue( m_grid->GetGridCursorRow(),
  113. DATASHEET_FIELD );
  114. GetAssociatedDocument( m_dlg, datasheet_uri, &m_dlg->Prj(),
  115. PROJECT_SCH::SchSearchS( &m_dlg->Prj() ) );
  116. }
  117. else
  118. {
  119. // We have grid tricks events to show/hide the columns from the popup menu
  120. // and we need to make sure the data model is updated to match the grid,
  121. // so do it through our code instead
  122. if( event.GetId() >= GRIDTRICKS_FIRST_SHOWHIDE )
  123. {
  124. // Pop-up column order is the order of the shown fields, not the
  125. // fieldsCtrl order
  126. int col = event.GetId() - GRIDTRICKS_FIRST_SHOWHIDE;
  127. bool show = !m_dataModel->GetShowColumn( col );
  128. // Convert data model column to by iterating over m_fieldsCtrl rows
  129. // and finding the matching field name
  130. wxString fieldName = m_dataModel->GetColFieldName( col );
  131. for( int row = 0; row < m_fieldsCtrl->GetItemCount(); row++ )
  132. {
  133. if( m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN ) == fieldName )
  134. {
  135. if( m_grid->CommitPendingChanges( false ) )
  136. m_fieldsCtrl->SetToggleValue( show, row, SHOW_FIELD_COLUMN );
  137. break;
  138. }
  139. }
  140. }
  141. else
  142. {
  143. GRID_TRICKS::doPopupSelection( event );
  144. }
  145. }
  146. }
  147. DIALOG_SHIM* m_dlg;
  148. wxDataViewListCtrl* m_fieldsCtrl;
  149. FIELDS_EDITOR_GRID_DATA_MODEL* m_dataModel;
  150. };
  151. DIALOG_SYMBOL_FIELDS_TABLE::DIALOG_SYMBOL_FIELDS_TABLE( SCH_EDIT_FRAME* parent ) :
  152. DIALOG_SYMBOL_FIELDS_TABLE_BASE( parent ),
  153. m_currentBomPreset( nullptr ),
  154. m_lastSelectedBomPreset( nullptr ),
  155. m_parent( parent ),
  156. m_schSettings( parent->Schematic().Settings() )
  157. {
  158. // Get all symbols from the list of schematic sheets
  159. m_parent->Schematic().BuildUnorderedSheetList().GetSymbols( m_symbolsList, false );
  160. m_bRefresh->SetBitmap( KiBitmapBundle( BITMAPS::small_refresh ) );
  161. m_bRefreshPreview->SetBitmap( KiBitmapBundle( BITMAPS::small_refresh ) );
  162. m_browseButton->SetBitmap( KiBitmapBundle( BITMAPS::small_folder ) );
  163. m_addFieldButton->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
  164. m_removeFieldButton->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
  165. m_renameFieldButton->SetBitmap( KiBitmapBundle( BITMAPS::small_edit ) );
  166. m_removeFieldButton->Enable( false );
  167. m_renameFieldButton->Enable( false );
  168. m_bomPresetsLabel->SetFont( KIUI::GetInfoFont( this ) );
  169. m_labelBomExportPresets->SetFont( KIUI::GetInfoFont( this ) );
  170. m_fieldsCtrl->AppendTextColumn( _( "Field" ), wxDATAVIEW_CELL_INERT, 0, wxALIGN_LEFT, 0 );
  171. m_fieldsCtrl->AppendTextColumn( _( "Label" ), wxDATAVIEW_CELL_EDITABLE, 0, wxALIGN_LEFT, 0 );
  172. m_fieldsCtrl->AppendToggleColumn( _( "Show" ), wxDATAVIEW_CELL_ACTIVATABLE, 0,
  173. wxALIGN_CENTER, 0 );
  174. m_fieldsCtrl->AppendToggleColumn( _( "Group By" ), wxDATAVIEW_CELL_ACTIVATABLE, 0,
  175. wxALIGN_CENTER, 0 );
  176. // GTK asserts if the number of columns doesn't match the data, but we still don't want
  177. // to display the canonical names. So we'll insert a column for them, but keep it 0 width.
  178. m_fieldsCtrl->AppendTextColumn( _( "Name" ), wxDATAVIEW_CELL_INERT, 0, wxALIGN_LEFT, 0 );
  179. // SetWidth( wxCOL_WIDTH_AUTOSIZE ) fails here on GTK, so we calculate the title sizes and
  180. // set the column widths ourselves.
  181. wxDataViewColumn* column = m_fieldsCtrl->GetColumn( SHOW_FIELD_COLUMN );
  182. m_showColWidth = KIUI::GetTextSize( column->GetTitle(), m_fieldsCtrl ).x + COLUMN_MARGIN;
  183. column->SetMinWidth( m_showColWidth );
  184. column = m_fieldsCtrl->GetColumn( GROUP_BY_COLUMN );
  185. m_groupByColWidth = KIUI::GetTextSize( column->GetTitle(), m_fieldsCtrl ).x + COLUMN_MARGIN;
  186. column->SetMinWidth( m_groupByColWidth );
  187. // The fact that we're a list should keep the control from reserving space for the
  188. // expander buttons... but it doesn't. Fix by forcing the indent to 0.
  189. m_fieldsCtrl->SetIndent( 0 );
  190. m_filter->SetDescriptiveText( _( "Filter" ) );
  191. m_dataModel = new FIELDS_EDITOR_GRID_DATA_MODEL( m_symbolsList );
  192. LoadFieldNames(); // loads rows into m_fieldsCtrl and columns into m_dataModel
  193. // Now that the fields are loaded we can set the initial location of the splitter
  194. // based on the list width. Again, SetWidth( wxCOL_WIDTH_AUTOSIZE ) fails us on GTK.
  195. m_fieldNameColWidth = 0;
  196. m_labelColWidth = 0;
  197. int colWidth = 0;
  198. for( int row = 0; row < m_fieldsCtrl->GetItemCount(); ++row )
  199. {
  200. const wxString& displayName = m_fieldsCtrl->GetTextValue( row, DISPLAY_NAME_COLUMN );
  201. colWidth = std::max( colWidth, KIUI::GetTextSize( displayName, m_fieldsCtrl ).x );
  202. const wxString& label = m_fieldsCtrl->GetTextValue( row, LABEL_COLUMN );
  203. colWidth = std::max( colWidth, KIUI::GetTextSize( label, m_fieldsCtrl ).x );
  204. }
  205. m_fieldNameColWidth = colWidth + 20;
  206. m_labelColWidth = colWidth + 20;
  207. int fieldsMinWidth = m_fieldNameColWidth + m_labelColWidth + m_groupByColWidth + m_showColWidth;
  208. m_fieldsCtrl->GetColumn( DISPLAY_NAME_COLUMN )->SetWidth( m_fieldNameColWidth );
  209. m_fieldsCtrl->GetColumn( LABEL_COLUMN )->SetWidth( m_labelColWidth );
  210. // This is used for data only. Don't show it to the user.
  211. m_fieldsCtrl->GetColumn( FIELD_NAME_COLUMN )->SetHidden( true );
  212. m_splitterMainWindow->SetMinimumPaneSize( fieldsMinWidth );
  213. m_splitterMainWindow->SetSashPosition( fieldsMinWidth + 40 );
  214. m_grid->UseNativeColHeader( true );
  215. m_grid->SetTable( m_dataModel, true );
  216. // must be done after SetTable(), which appears to re-set it
  217. m_grid->SetSelectionMode( wxGrid::wxGridSelectCells );
  218. // add Cut, Copy, and Paste to wxGrid
  219. m_grid->PushEventHandler( new FIELDS_EDITOR_GRID_TRICKS( this, m_grid, m_fieldsCtrl,
  220. m_dataModel ) );
  221. // give a bit more room for comboboxes
  222. m_grid->SetDefaultRowSize( m_grid->GetDefaultRowSize() + 4 );
  223. // Load our BOM view presets
  224. SetUserBomPresets( m_schSettings.m_BomPresets );
  225. ApplyBomPreset( m_schSettings.m_BomSettings );
  226. syncBomPresetSelection();
  227. // Load BOM export format presets
  228. SetUserBomFmtPresets( m_schSettings.m_BomFmtPresets );
  229. ApplyBomFmtPreset( m_schSettings.m_BomFmtSettings );
  230. syncBomFmtPresetSelection();
  231. SetInitialFocus( m_grid );
  232. m_grid->ClearSelection();
  233. SetupStandardButtons();
  234. finishDialogSettings();
  235. EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
  236. EESCHEMA_SETTINGS::PANEL_FIELD_EDITOR& panelCfg = cfg->m_FieldEditorPanel;
  237. wxSize dlgSize( panelCfg.width > 0 ? panelCfg.width : horizPixelsFromDU( 600 ),
  238. panelCfg.height > 0 ? panelCfg.height : vertPixelsFromDU( 300 ) );
  239. SetSize( dlgSize );
  240. m_nbPages->SetSelection( cfg->m_FieldEditorPanel.page );
  241. switch( cfg->m_FieldEditorPanel.selection_mode )
  242. {
  243. case 0: m_radioHighlight->SetValue( true ); break;
  244. case 1: m_radioSelect->SetValue( true ); break;
  245. case 2: m_radioOff->SetValue( true ); break;
  246. }
  247. switch( cfg->m_FieldEditorPanel.scope )
  248. {
  249. case SCOPE::SCOPE_ALL: m_radioProject->SetValue( true ); break;
  250. case SCOPE::SCOPE_SHEET: m_radioCurrentSheet->SetValue( true ); break;
  251. case SCOPE::SCOPE_SHEET_RECURSIVE: m_radioRecursive->SetValue( true ); break;
  252. }
  253. m_outputFileName->SetValue( m_schSettings.m_BomExportFileName );
  254. Center();
  255. // Connect Events
  256. m_grid->Connect( wxEVT_GRID_COL_SORT,
  257. wxGridEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnColSort ), nullptr, this );
  258. m_grid->Connect( wxEVT_GRID_COL_MOVE,
  259. wxGridEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnColMove ), nullptr, this );
  260. m_cbBomPresets->Bind( wxEVT_CHOICE, &DIALOG_SYMBOL_FIELDS_TABLE::onBomPresetChanged, this );
  261. m_cbBomFmtPresets->Bind( wxEVT_CHOICE, &DIALOG_SYMBOL_FIELDS_TABLE::onBomFmtPresetChanged, this );
  262. m_fieldsCtrl->Bind( wxEVT_DATAVIEW_ITEM_VALUE_CHANGED,
  263. &DIALOG_SYMBOL_FIELDS_TABLE::OnColLabelChange, this );
  264. // Start listening for schematic changes
  265. m_parent->Schematic().AddListener( this );
  266. }
  267. void DIALOG_SYMBOL_FIELDS_TABLE::SetupColumnProperties( int aCol )
  268. {
  269. wxGridCellAttr* attr = new wxGridCellAttr;
  270. attr->SetReadOnly( false );
  271. // Set some column types to specific editors
  272. if( m_dataModel->ColIsReference( aCol ) )
  273. {
  274. attr->SetReadOnly();
  275. m_grid->SetColAttr( aCol, attr );
  276. }
  277. else if( m_dataModel->GetColFieldName( aCol ) == GetCanonicalFieldName( FOOTPRINT_FIELD ) )
  278. {
  279. attr->SetEditor( new GRID_CELL_FPID_EDITOR( this, wxEmptyString ) );
  280. m_grid->SetColAttr( aCol, attr );
  281. }
  282. else if( m_dataModel->GetColFieldName( aCol ) == GetCanonicalFieldName( DATASHEET_FIELD ) )
  283. {
  284. // set datasheet column viewer button
  285. attr->SetEditor(
  286. new GRID_CELL_URL_EDITOR( this, PROJECT_SCH::SchSearchS( &Prj() ) ) );
  287. m_grid->SetColAttr( aCol, attr );
  288. }
  289. else if( m_dataModel->ColIsQuantity( aCol ) || m_dataModel->ColIsItemNumber( aCol ) )
  290. {
  291. attr->SetReadOnly();
  292. m_grid->SetColAttr( aCol, attr );
  293. m_grid->SetColFormatNumber( aCol );
  294. }
  295. else if( m_dataModel->ColIsAttribute( aCol ) )
  296. {
  297. attr->SetAlignment( wxALIGN_CENTER, wxALIGN_CENTER );
  298. m_grid->SetColAttr( aCol, attr );
  299. m_grid->SetColFormatBool( aCol );
  300. }
  301. else if( IsTextVar( m_dataModel->GetColFieldName( aCol ) ) )
  302. {
  303. attr->SetReadOnly();
  304. m_grid->SetColAttr( aCol, attr );
  305. }
  306. else
  307. {
  308. attr->SetEditor( m_grid->GetDefaultEditor() );
  309. m_grid->SetColAttr( aCol, attr );
  310. m_grid->SetColFormatCustom( aCol, wxGRID_VALUE_STRING );
  311. }
  312. }
  313. void DIALOG_SYMBOL_FIELDS_TABLE::SetupAllColumnProperties()
  314. {
  315. EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
  316. wxSize defaultDlgSize = ConvertDialogToPixels( wxSize( 600, 300 ) );
  317. // Restore column sorting order and widths
  318. m_grid->AutoSizeColumns( false );
  319. int sortCol = 0;
  320. bool sortAscending = true;
  321. for( int col = 0; col < m_grid->GetNumberCols(); ++col )
  322. {
  323. SetupColumnProperties( col );
  324. if( col == m_dataModel->GetSortCol() )
  325. {
  326. sortCol = col;
  327. sortAscending = m_dataModel->GetSortAsc();
  328. }
  329. }
  330. // sync m_grid's column visibilities to Show checkboxes in m_fieldsCtrl
  331. for( int i = 0; i < m_fieldsCtrl->GetItemCount(); ++i )
  332. {
  333. int col = m_dataModel->GetFieldNameCol( m_fieldsCtrl->GetTextValue( i, FIELD_NAME_COLUMN ) );
  334. if( col == -1 )
  335. continue;
  336. bool show = m_fieldsCtrl->GetToggleValue( i, SHOW_FIELD_COLUMN );
  337. m_dataModel->SetShowColumn( col, show );
  338. if( show )
  339. {
  340. m_grid->ShowCol( col );
  341. std::string key( m_dataModel->GetColFieldName( col ).ToUTF8() );
  342. if( cfg->m_FieldEditorPanel.field_widths.count( key )
  343. && ( cfg->m_FieldEditorPanel.field_widths.at( key ) > 0 ) )
  344. {
  345. m_grid->SetColSize( col, cfg->m_FieldEditorPanel.field_widths.at( key ) );
  346. }
  347. else
  348. {
  349. int textWidth = m_dataModel->GetDataWidth( col ) + COLUMN_MARGIN;
  350. int maxWidth = defaultDlgSize.x / 3;
  351. m_grid->SetColSize( col, Clamp( 100, textWidth, maxWidth ) );
  352. }
  353. }
  354. else
  355. {
  356. m_grid->HideCol( col );
  357. }
  358. }
  359. m_dataModel->SetSorting( sortCol, sortAscending );
  360. m_grid->SetSortingColumn( sortCol, sortAscending );
  361. }
  362. DIALOG_SYMBOL_FIELDS_TABLE::~DIALOG_SYMBOL_FIELDS_TABLE()
  363. {
  364. // Disconnect Events
  365. m_grid->Disconnect( wxEVT_GRID_COL_SORT,
  366. wxGridEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnColSort ), nullptr,
  367. this );
  368. m_grid->Disconnect( wxEVT_GRID_COL_SORT,
  369. wxGridEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnColMove ), nullptr,
  370. this );
  371. // Delete the GRID_TRICKS.
  372. m_grid->PopEventHandler( true );
  373. // we gave ownership of m_dataModel to the wxGrid...
  374. }
  375. bool DIALOG_SYMBOL_FIELDS_TABLE::TransferDataToWindow()
  376. {
  377. if( !wxDialog::TransferDataFromWindow() )
  378. return false;
  379. TOOL_MANAGER* toolMgr = m_parent->GetToolManager();
  380. EE_SELECTION_TOOL* selectionTool = toolMgr->GetTool<EE_SELECTION_TOOL>();
  381. EE_SELECTION& selection = selectionTool->GetSelection();
  382. SCH_SYMBOL* symbol = nullptr;
  383. UpdateScope();
  384. if( selection.GetSize() == 1 )
  385. {
  386. EDA_ITEM* item = selection.Front();
  387. if( item->Type() == SCH_SYMBOL_T )
  388. symbol = (SCH_SYMBOL*) item;
  389. else if( item->GetParent() && item->GetParent()->Type() == SCH_SYMBOL_T )
  390. symbol = (SCH_SYMBOL*) item->GetParent();
  391. }
  392. if( symbol )
  393. {
  394. for( int row = 0; row < m_dataModel->GetNumberRows(); ++row )
  395. {
  396. std::vector<SCH_REFERENCE> references = m_dataModel->GetRowReferences( row );
  397. bool found = false;
  398. for( const SCH_REFERENCE& ref : references )
  399. {
  400. if( ref.GetSymbol() == symbol )
  401. {
  402. found = true;
  403. break;
  404. }
  405. }
  406. if( found )
  407. {
  408. // Find the value column and the reference column if they're shown
  409. int valueCol = -1;
  410. int refCol = -1;
  411. int anyCol = -1;
  412. for( int col = 0; col < m_dataModel->GetNumberCols(); col++ )
  413. {
  414. if( m_dataModel->ColIsValue( col ) )
  415. valueCol = col;
  416. else if( m_dataModel->ColIsReference( col ) )
  417. refCol = col;
  418. else if( anyCol == -1 && m_dataModel->GetShowColumn( col ) )
  419. anyCol = col;
  420. }
  421. if( valueCol != -1 && m_dataModel->GetShowColumn( valueCol ) )
  422. m_grid->GoToCell( row, valueCol );
  423. else if( refCol != -1 && m_dataModel->GetShowColumn( refCol ) )
  424. m_grid->GoToCell( row, refCol );
  425. else if( anyCol != -1 )
  426. m_grid->GoToCell( row, anyCol );
  427. break;
  428. }
  429. }
  430. }
  431. // We don't want table range selection events to happen until we've loaded the data or we
  432. // we'll clear our selection as the grid is built before the code above can get the
  433. // user's current selection.
  434. EnableSelectionEvents();
  435. return true;
  436. }
  437. bool DIALOG_SYMBOL_FIELDS_TABLE::TransferDataFromWindow()
  438. {
  439. if( !m_grid->CommitPendingChanges() )
  440. return false;
  441. if( !wxDialog::TransferDataFromWindow() )
  442. return false;
  443. SCH_COMMIT commit( m_parent );
  444. SCH_SHEET_PATH currentSheet = m_parent->GetCurrentSheet();
  445. std::function<void( SCH_SYMBOL&, SCH_SHEET_PATH & aPath )> changeHandler =
  446. [&commit]( SCH_SYMBOL& aSymbol, SCH_SHEET_PATH& aPath ) -> void
  447. {
  448. commit.Modify( &aSymbol, aPath.LastScreen() );
  449. };
  450. m_dataModel->ApplyData( changeHandler );
  451. commit.Push( wxS( "Symbol Fields Table Edit" ) );
  452. // Reset the view to where we left the user
  453. m_parent->SetCurrentSheet( currentSheet );
  454. m_parent->SyncView();
  455. m_parent->Refresh();
  456. m_parent->OnModify();
  457. return true;
  458. }
  459. void DIALOG_SYMBOL_FIELDS_TABLE::AddField( const wxString& aFieldName, const wxString& aLabelValue,
  460. bool show, bool groupBy, bool addedByUser )
  461. {
  462. // Users can add fields with variable names that match the special names in the grid,
  463. // e.g. ${QUANTITY} so make sure we don't add them twice
  464. for( int i = 0; i < m_fieldsCtrl->GetItemCount(); i++ )
  465. {
  466. if( m_fieldsCtrl->GetTextValue( i, FIELD_NAME_COLUMN ) == aFieldName )
  467. return;
  468. }
  469. m_dataModel->AddColumn( aFieldName, aLabelValue, addedByUser );
  470. wxVector<wxVariant> fieldsCtrlRow;
  471. std::string key( aFieldName.ToUTF8() );
  472. // Don't change these to emplace_back: some versions of wxWidgets don't support it
  473. fieldsCtrlRow.push_back( wxVariant( aFieldName ) );
  474. fieldsCtrlRow.push_back( wxVariant( aLabelValue ) );
  475. fieldsCtrlRow.push_back( wxVariant( show ) );
  476. fieldsCtrlRow.push_back( wxVariant( groupBy ) );
  477. fieldsCtrlRow.push_back( wxVariant( aFieldName ) );
  478. m_fieldsCtrl->AppendItem( fieldsCtrlRow );
  479. wxGridTableMessage msg( m_dataModel, wxGRIDTABLE_NOTIFY_COLS_APPENDED, 1 );
  480. m_grid->ProcessTableMessage( msg );
  481. }
  482. void DIALOG_SYMBOL_FIELDS_TABLE::LoadFieldNames()
  483. {
  484. // Add mandatory fields first
  485. for( int i = 0; i < MANDATORY_FIELDS; ++i )
  486. {
  487. bool show = false;
  488. bool groupBy = false;
  489. switch( i )
  490. {
  491. case REFERENCE_FIELD:
  492. case VALUE_FIELD:
  493. case FOOTPRINT_FIELD:
  494. show = true;
  495. groupBy = true;
  496. break;
  497. case DATASHEET_FIELD:
  498. show = true;
  499. groupBy = false;
  500. break;
  501. }
  502. AddField( TEMPLATE_FIELDNAME::GetDefaultFieldName( i ),
  503. TEMPLATE_FIELDNAME::GetDefaultFieldName( i, true ), show, groupBy );
  504. }
  505. // Generated fields present only in the fields table
  506. AddField( FIELDS_EDITOR_GRID_DATA_MODEL::QUANTITY_VARIABLE, _( "Qty" ), true, false );
  507. AddField( FIELDS_EDITOR_GRID_DATA_MODEL::ITEM_NUMBER_VARIABLE, _( "#" ), true, false );
  508. // User fields next
  509. std::set<wxString> userFieldNames;
  510. for( unsigned i = 0; i < m_symbolsList.GetCount(); ++i )
  511. {
  512. SCH_SYMBOL* symbol = m_symbolsList[ i ].GetSymbol();
  513. for( int j = MANDATORY_FIELDS; j < symbol->GetFieldCount(); ++j )
  514. userFieldNames.insert( symbol->GetFields()[j].GetName() );
  515. }
  516. for( const wxString& fieldName : userFieldNames )
  517. AddField( fieldName, GetTextVars( fieldName ), true, false );
  518. // Add any templateFieldNames which aren't already present in the userFieldNames
  519. for( const TEMPLATE_FIELDNAME& templateFieldname :
  520. m_schSettings.m_TemplateFieldNames.GetTemplateFieldNames() )
  521. {
  522. if( userFieldNames.count( templateFieldname.m_Name ) == 0 )
  523. {
  524. AddField( templateFieldname.m_Name, GetTextVars( templateFieldname.m_Name ), false,
  525. false );
  526. }
  527. }
  528. }
  529. void DIALOG_SYMBOL_FIELDS_TABLE::OnAddField( wxCommandEvent& event )
  530. {
  531. wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Add Field" ) );
  532. if( dlg.ShowModal() != wxID_OK )
  533. return;
  534. wxString fieldName = dlg.GetValue();
  535. if( fieldName.IsEmpty() )
  536. {
  537. DisplayError( this, _( "Field must have a name." ) );
  538. return;
  539. }
  540. for( int i = 0; i < m_dataModel->GetNumberCols(); ++i )
  541. {
  542. if( fieldName == m_dataModel->GetColFieldName( i ) )
  543. {
  544. DisplayError( this, wxString::Format( _( "Field name '%s' already in use." ),
  545. fieldName ) );
  546. return;
  547. }
  548. }
  549. AddField( fieldName, GetTextVars( fieldName ), true, false, true );
  550. SetupColumnProperties( m_dataModel->GetColsCount() - 1 );
  551. syncBomPresetSelection();
  552. }
  553. void DIALOG_SYMBOL_FIELDS_TABLE::OnRemoveField( wxCommandEvent& event )
  554. {
  555. int col = -1;
  556. int row = m_fieldsCtrl->GetSelectedRow();
  557. // Should never occur: "Remove Field..." button should be disabled if invalid selection
  558. // via OnFieldsCtrlSelectionChanged()
  559. wxCHECK_RET( row != -1, wxS( "Some user defined field must be selected first" ) );
  560. wxCHECK_RET( row >= MANDATORY_FIELDS, wxS( "Mandatory fields cannot be removed" ) );
  561. wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
  562. wxString displayName = m_fieldsCtrl->GetTextValue( row, DISPLAY_NAME_COLUMN );
  563. wxString confirm_msg = wxString::Format( _( "Are you sure you want to remove the field '%s'?" ),
  564. displayName );
  565. if( !IsOK( this, confirm_msg ) )
  566. return;
  567. for( int i = 0; i < m_dataModel->GetNumberCols(); ++i )
  568. {
  569. if( fieldName == m_dataModel->GetColFieldName( i ) )
  570. col = i;
  571. }
  572. m_fieldsCtrl->DeleteItem( row );
  573. m_dataModel->RemoveColumn( col );
  574. // Make selection and update the state of "Remove field..." button via OnFieldsCtrlSelectionChanged()
  575. // Safe to decrement row index because we always have mandatory fields
  576. m_fieldsCtrl->SelectRow( --row );
  577. if( row < MANDATORY_FIELDS )
  578. {
  579. m_removeFieldButton->Enable( false );
  580. m_renameFieldButton->Enable( false );
  581. }
  582. wxGridTableMessage msg( m_dataModel, wxGRIDTABLE_NOTIFY_COLS_DELETED, col, 1 );
  583. m_grid->ProcessTableMessage( msg );
  584. syncBomPresetSelection();
  585. }
  586. void DIALOG_SYMBOL_FIELDS_TABLE::OnRenameField( wxCommandEvent& event )
  587. {
  588. int row = m_fieldsCtrl->GetSelectedRow();
  589. wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
  590. // Should never occur: "Rename Field..." button should be disabled if invalid selection
  591. // via OnFieldsCtrlSelectionChanged()
  592. wxCHECK_RET( row != -1, wxS( "Some user defined field must be selected first" ) );
  593. wxCHECK_RET( row >= MANDATORY_FIELDS, wxS( "Mandatory fields cannot be renamed" ) );
  594. wxCHECK_RET( !fieldName.IsEmpty(), wxS( "Field must have a name" ) );
  595. int col = m_dataModel->GetFieldNameCol( fieldName );
  596. wxCHECK_RET( col != -1, wxS( "Existing field name missing from data model" ) );
  597. wxTextEntryDialog dlg( this, _( "New field name:" ), _( "Rename Field" ) );
  598. if( dlg.ShowModal() != wxID_OK )
  599. return;
  600. wxString newFieldName = dlg.GetValue();
  601. // No change, no-op
  602. if( newFieldName == fieldName )
  603. return;
  604. // New field name already exists
  605. if( m_dataModel->GetFieldNameCol( newFieldName ) != -1 )
  606. {
  607. wxString confirm_msg = wxString::Format( _( "Field name %s already exists." ),
  608. newFieldName );
  609. DisplayError( this, confirm_msg );
  610. return;
  611. }
  612. m_dataModel->RenameColumn( col, newFieldName );
  613. m_fieldsCtrl->SetTextValue( newFieldName, row, DISPLAY_NAME_COLUMN );
  614. m_fieldsCtrl->SetTextValue( newFieldName, row, FIELD_NAME_COLUMN );
  615. m_fieldsCtrl->SetTextValue( newFieldName, row, LABEL_COLUMN );
  616. syncBomPresetSelection();
  617. }
  618. void DIALOG_SYMBOL_FIELDS_TABLE::OnFilterText( wxCommandEvent& aEvent )
  619. {
  620. m_dataModel->SetFilter( m_filter->GetValue() );
  621. m_dataModel->RebuildRows();
  622. m_grid->ForceRefresh();
  623. syncBomPresetSelection();
  624. }
  625. void DIALOG_SYMBOL_FIELDS_TABLE::OnFilterMouseMoved( wxMouseEvent& aEvent )
  626. {
  627. wxPoint pos = aEvent.GetPosition();
  628. wxRect ctrlRect = m_filter->GetScreenRect();
  629. int buttonWidth = ctrlRect.GetHeight(); // Presume buttons are square
  630. // TODO: restore cursor when mouse leaves the filter field (or is it a MSW bug?)
  631. if( m_filter->IsSearchButtonVisible() && pos.x < buttonWidth )
  632. SetCursor( wxCURSOR_ARROW );
  633. else if( m_filter->IsCancelButtonVisible() && pos.x > ctrlRect.GetWidth() - buttonWidth )
  634. SetCursor( wxCURSOR_ARROW );
  635. else
  636. SetCursor( wxCURSOR_IBEAM );
  637. }
  638. void DIALOG_SYMBOL_FIELDS_TABLE::OnFieldsCtrlSelectionChanged( wxDataViewEvent& event )
  639. {
  640. int row = m_fieldsCtrl->GetSelectedRow();
  641. if( row >= MANDATORY_FIELDS )
  642. {
  643. m_removeFieldButton->Enable( true );
  644. m_renameFieldButton->Enable( true );
  645. }
  646. else
  647. {
  648. m_removeFieldButton->Enable( false );
  649. m_renameFieldButton->Enable( false );
  650. }
  651. }
  652. void DIALOG_SYMBOL_FIELDS_TABLE::OnColumnItemToggled( wxDataViewEvent& event )
  653. {
  654. wxDataViewItem item = event.GetItem();
  655. int row = m_fieldsCtrl->ItemToRow( item );
  656. int col = event.GetColumn();
  657. switch ( col )
  658. {
  659. case SHOW_FIELD_COLUMN:
  660. {
  661. wxString name = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
  662. bool value = m_fieldsCtrl->GetToggleValue( row, col );
  663. int dataCol = m_dataModel->GetFieldNameCol( name );
  664. m_dataModel->SetShowColumn( dataCol, value );
  665. if( dataCol != -1 )
  666. {
  667. if( value )
  668. m_grid->ShowCol( dataCol );
  669. else
  670. m_grid->HideCol( dataCol );
  671. }
  672. break;
  673. }
  674. case GROUP_BY_COLUMN:
  675. {
  676. wxString name = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
  677. bool value = m_fieldsCtrl->GetToggleValue( row, col );
  678. int dataCol = m_dataModel->GetFieldNameCol( name );
  679. if( m_dataModel->ColIsQuantity( dataCol ) && value )
  680. {
  681. DisplayError( this, _( "The Quantity column cannot be grouped by." ) );
  682. value = false;
  683. m_fieldsCtrl->SetToggleValue( value, row, col );
  684. }
  685. if( m_dataModel->ColIsItemNumber( dataCol ) && value )
  686. {
  687. DisplayError( this, _( "The Item Number column cannot be grouped by." ) );
  688. value = false;
  689. m_fieldsCtrl->SetToggleValue( value, row, col );
  690. }
  691. wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
  692. m_dataModel->SetGroupColumn( m_dataModel->GetFieldNameCol( fieldName ), value );
  693. m_dataModel->RebuildRows();
  694. m_grid->ForceRefresh();
  695. break;
  696. }
  697. default:
  698. break;
  699. }
  700. syncBomPresetSelection();
  701. }
  702. void DIALOG_SYMBOL_FIELDS_TABLE::OnGroupSymbolsToggled( wxCommandEvent& event )
  703. {
  704. m_dataModel->SetGroupingEnabled( m_groupSymbolsBox->GetValue() );
  705. m_dataModel->RebuildRows();
  706. m_grid->ForceRefresh();
  707. syncBomPresetSelection();
  708. }
  709. void DIALOG_SYMBOL_FIELDS_TABLE::OnExcludeDNPToggled( wxCommandEvent& event )
  710. {
  711. m_dataModel->SetExcludeDNP( m_checkExcludeDNP->GetValue() );
  712. m_dataModel->RebuildRows();
  713. m_grid->ForceRefresh();
  714. syncBomPresetSelection();
  715. }
  716. void DIALOG_SYMBOL_FIELDS_TABLE::OnShowExcludedToggled( wxCommandEvent& event )
  717. {
  718. m_dataModel->SetIncludeExcludedFromBOM( m_checkShowExcluded->GetValue() );
  719. m_dataModel->RebuildRows();
  720. m_grid->ForceRefresh();
  721. syncBomPresetSelection();
  722. }
  723. void DIALOG_SYMBOL_FIELDS_TABLE::OnColSort( wxGridEvent& aEvent )
  724. {
  725. int sortCol = aEvent.GetCol();
  726. std::string key( m_dataModel->GetColFieldName( sortCol ).ToUTF8() );
  727. bool ascending;
  728. // Don't sort by item number, it is generated by the sort
  729. if( m_dataModel->ColIsItemNumber( sortCol ) )
  730. {
  731. aEvent.Veto();
  732. return;
  733. }
  734. // This is bonkers, but wxWidgets doesn't tell us ascending/descending in the event, and
  735. // if we ask it will give us pre-event info.
  736. if( m_grid->IsSortingBy( sortCol ) )
  737. {
  738. // same column; invert ascending
  739. ascending = !m_grid->IsSortOrderAscending();
  740. }
  741. else
  742. {
  743. // different column; start with ascending
  744. ascending = true;
  745. }
  746. m_dataModel->SetSorting( sortCol, ascending );
  747. m_dataModel->RebuildRows();
  748. m_grid->ForceRefresh();
  749. syncBomPresetSelection();
  750. }
  751. void DIALOG_SYMBOL_FIELDS_TABLE::OnColMove( wxGridEvent& aEvent )
  752. {
  753. int origPos = aEvent.GetCol();
  754. // Save column widths since the setup function uses the saved config values
  755. EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
  756. for( int i = 0; i < m_grid->GetNumberCols(); i++ )
  757. {
  758. if( m_grid->IsColShown( i ) )
  759. {
  760. std::string fieldName( m_dataModel->GetColFieldName( i ).ToUTF8() );
  761. cfg->m_FieldEditorPanel.field_widths[fieldName] = m_grid->GetColSize( i );
  762. }
  763. }
  764. CallAfter(
  765. [origPos, this]()
  766. {
  767. int newPos = m_grid->GetColPos( origPos );
  768. m_dataModel->MoveColumn( origPos, newPos );
  769. // "Unmove" the column since we've moved the column internally
  770. m_grid->ResetColPos();
  771. // We need to reset all the column attr's to the correct column order
  772. SetupAllColumnProperties();
  773. m_grid->ForceRefresh();
  774. } );
  775. syncBomPresetSelection();
  776. }
  777. void DIALOG_SYMBOL_FIELDS_TABLE::OnColLabelChange( wxDataViewEvent& aEvent )
  778. {
  779. wxDataViewItem item = aEvent.GetItem();
  780. int row = m_fieldsCtrl->ItemToRow( item );
  781. wxString label = m_fieldsCtrl->GetTextValue( row, LABEL_COLUMN );
  782. wxString fieldName = m_fieldsCtrl->GetTextValue( row, FIELD_NAME_COLUMN );
  783. int col = m_dataModel->GetFieldNameCol( fieldName );
  784. if( col != -1 )
  785. m_dataModel->SetColLabelValue( col, label );
  786. syncBomPresetSelection();
  787. aEvent.Skip();
  788. m_grid->ForceRefresh();
  789. }
  790. void DIALOG_SYMBOL_FIELDS_TABLE::OnTableValueChanged( wxGridEvent& aEvent )
  791. {
  792. m_grid->ForceRefresh();
  793. }
  794. void DIALOG_SYMBOL_FIELDS_TABLE::OnTableColSize( wxGridSizeEvent& aEvent )
  795. {
  796. int col = aEvent.GetRowOrCol();
  797. std::string key( m_dataModel->GetColFieldName( col ).ToUTF8() );
  798. aEvent.Skip();
  799. m_grid->ForceRefresh();
  800. }
  801. void DIALOG_SYMBOL_FIELDS_TABLE::OnRegroupSymbols( wxCommandEvent& aEvent )
  802. {
  803. m_dataModel->RebuildRows();
  804. m_grid->ForceRefresh();
  805. }
  806. void DIALOG_SYMBOL_FIELDS_TABLE::OnScopeChanged( wxCommandEvent& aEvent )
  807. {
  808. UpdateScope();
  809. }
  810. void DIALOG_SYMBOL_FIELDS_TABLE::UpdateScope()
  811. {
  812. m_dataModel->SetPath( m_parent->GetCurrentSheet() );
  813. if( m_radioProject->GetValue() )
  814. m_dataModel->SetScope( FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_ALL );
  815. else if( m_radioCurrentSheet->GetValue() )
  816. m_dataModel->SetScope( FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_SHEET );
  817. else if( m_radioRecursive->GetValue() )
  818. m_dataModel->SetScope( FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_SHEET_RECURSIVE );
  819. m_dataModel->RebuildRows();
  820. }
  821. void DIALOG_SYMBOL_FIELDS_TABLE::OnTableCellClick( wxGridEvent& event )
  822. {
  823. if( m_dataModel->ColIsReference( event.GetCol() ) )
  824. {
  825. m_grid->ClearSelection();
  826. m_dataModel->ExpandCollapseRow( event.GetRow() );
  827. m_grid->SetGridCursor( event.GetRow(), event.GetCol() );
  828. }
  829. else
  830. {
  831. event.Skip();
  832. }
  833. }
  834. void DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected( wxGridRangeSelectEvent& aEvent )
  835. {
  836. // Cross-probing should only work in Edit page
  837. if( m_nbPages->GetSelection() != 0 )
  838. return;
  839. // Multi-select can grab the rows that are expanded child refs, and also the row
  840. // containing the list of all child refs. Make sure we add refs/symbols uniquely
  841. std::set<SCH_REFERENCE> refs;
  842. std::set<SCH_ITEM*> symbols;
  843. // This handler handles selecting and deselecting
  844. if( aEvent.Selecting() )
  845. {
  846. for( int i = aEvent.GetTopRow(); i <= aEvent.GetBottomRow(); i++ )
  847. {
  848. for( const SCH_REFERENCE& ref : m_dataModel->GetRowReferences( i ) )
  849. refs.insert( ref );
  850. }
  851. for( const SCH_REFERENCE& ref : refs )
  852. symbols.insert( ref.GetSymbol() );
  853. }
  854. if( m_radioHighlight->GetValue() )
  855. {
  856. SCH_EDITOR_CONTROL* editor = m_parent->GetToolManager()->GetTool<SCH_EDITOR_CONTROL>();
  857. if( refs.size() > 0 )
  858. {
  859. // Use of full path based on UUID allows select of not yet annotated or duplicated symbols
  860. wxString symbol_path = refs.begin()->GetFullPath();
  861. // Focus only handles one item at this time
  862. editor->FindSymbolAndItem( &symbol_path, nullptr, true, HIGHLIGHT_SYMBOL,
  863. wxEmptyString );
  864. }
  865. else
  866. {
  867. m_parent->FocusOnItem( nullptr );
  868. }
  869. }
  870. else if( m_radioSelect->GetValue() )
  871. {
  872. EE_SELECTION_TOOL* selTool = m_parent->GetToolManager()->GetTool<EE_SELECTION_TOOL>();
  873. std::vector<SCH_ITEM*> items( symbols.begin(), symbols.end() );
  874. if( refs.size() > 0 )
  875. selTool->SyncSelection( refs.begin()->GetSheetPath(), nullptr, items );
  876. else
  877. selTool->ClearSelection();
  878. }
  879. }
  880. void DIALOG_SYMBOL_FIELDS_TABLE::OnTableItemContextMenu( wxGridEvent& event )
  881. {
  882. // TODO: Option to select footprint if FOOTPRINT column selected
  883. event.Skip();
  884. }
  885. void DIALOG_SYMBOL_FIELDS_TABLE::OnSizeFieldList( wxSizeEvent& event )
  886. {
  887. int width = KIPLATFORM::UI::GetUnobscuredSize( m_fieldsCtrl ).x
  888. - m_showColWidth
  889. - m_groupByColWidth;
  890. #ifdef __WXMAC__
  891. // TODO: something in wxWidgets 3.1.x pads checkbox columns with extra space. (It used to
  892. // also be that the width of the column would get set too wide (to 30), but that's patched in
  893. // our local wxWidgets fork.)
  894. width -= 50;
  895. #endif
  896. m_fieldNameColWidth = width / 2;
  897. m_labelColWidth = width = m_fieldNameColWidth;
  898. // GTK loses its head and messes these up when resizing the splitter bar:
  899. m_fieldsCtrl->GetColumn( SHOW_FIELD_COLUMN )->SetWidth( m_showColWidth );
  900. m_fieldsCtrl->GetColumn( GROUP_BY_COLUMN )->SetWidth( m_groupByColWidth );
  901. m_fieldsCtrl->GetColumn( FIELD_NAME_COLUMN )->SetHidden( true );
  902. m_fieldsCtrl->GetColumn( DISPLAY_NAME_COLUMN )->SetWidth( m_fieldNameColWidth );
  903. m_fieldsCtrl->GetColumn( LABEL_COLUMN )->SetWidth( m_labelColWidth );
  904. m_fieldsCtrl->Refresh(); // To refresh checkboxes on Windows.
  905. event.Skip();
  906. }
  907. void DIALOG_SYMBOL_FIELDS_TABLE::OnSaveAndContinue( wxCommandEvent& aEvent )
  908. {
  909. if( TransferDataFromWindow() )
  910. m_parent->SaveProject();
  911. }
  912. void DIALOG_SYMBOL_FIELDS_TABLE::OnPageChanged( wxNotebookEvent& event )
  913. {
  914. PreviewRefresh();
  915. }
  916. void DIALOG_SYMBOL_FIELDS_TABLE::OnPreviewRefresh( wxCommandEvent& event )
  917. {
  918. PreviewRefresh();
  919. syncBomFmtPresetSelection();
  920. }
  921. void DIALOG_SYMBOL_FIELDS_TABLE::PreviewRefresh()
  922. {
  923. m_dataModel->RebuildRows();
  924. m_textOutput->SetValue( m_dataModel->Export( GetCurrentBomFmtSettings() ) );
  925. }
  926. BOM_FMT_PRESET DIALOG_SYMBOL_FIELDS_TABLE::GetCurrentBomFmtSettings()
  927. {
  928. BOM_FMT_PRESET current;
  929. current.name = m_cbBomFmtPresets->GetStringSelection();
  930. current.fieldDelimiter = m_textFieldDelimiter->GetValue();
  931. current.stringDelimiter = m_textStringDelimiter->GetValue();
  932. current.refDelimiter = m_textRefDelimiter->GetValue();
  933. current.refRangeDelimiter = m_textRefRangeDelimiter->GetValue();
  934. current.keepTabs = m_checkKeepTabs->GetValue();
  935. current.keepLineBreaks = m_checkKeepLineBreaks->GetValue();
  936. return current;
  937. }
  938. void DIALOG_SYMBOL_FIELDS_TABLE::ShowEditTab()
  939. {
  940. m_nbPages->SetSelection( 0 );
  941. }
  942. void DIALOG_SYMBOL_FIELDS_TABLE::ShowExportTab()
  943. {
  944. m_nbPages->SetSelection( 1 );
  945. }
  946. void DIALOG_SYMBOL_FIELDS_TABLE::OnOutputFileBrowseClicked( wxCommandEvent& event )
  947. {
  948. // Build the absolute path of current output directory to preselect it in the file browser.
  949. wxString path = ExpandEnvVarSubstitutions( m_outputFileName->GetValue(), &Prj() );
  950. path = Prj().AbsolutePath( path );
  951. // Calculate the export filename
  952. wxFileName fn( Prj().AbsolutePath( m_parent->Schematic().GetFileName() ) );
  953. fn.SetExt( FILEEXT::CsvFileExtension );
  954. wxFileDialog saveDlg( this, _( "Bill of Materials Output File" ), path, fn.GetFullName(),
  955. FILEEXT::CsvFileWildcard(), wxFD_SAVE | wxFD_OVERWRITE_PROMPT );
  956. if( saveDlg.ShowModal() == wxID_CANCEL )
  957. return;
  958. wxFileName file = wxFileName( saveDlg.GetPath() );
  959. wxString defaultPath = fn.GetPathWithSep();
  960. wxString msg;
  961. msg.Printf( _( "Do you want to use a path relative to\n'%s'?" ), defaultPath );
  962. wxMessageDialog dialog( this, msg, _( "BOM Output File" ),
  963. wxYES_NO | wxICON_QUESTION | wxYES_DEFAULT );
  964. if( dialog.ShowModal() == wxID_YES )
  965. {
  966. if( !file.MakeRelativeTo( defaultPath ) )
  967. {
  968. wxMessageBox( _( "Cannot make path relative (target volume different from schematic "
  969. "file volume)!" ),
  970. _( "BOM Output File" ), wxOK | wxICON_ERROR );
  971. }
  972. }
  973. m_outputFileName->SetValue( file.GetFullPath() );
  974. }
  975. void DIALOG_SYMBOL_FIELDS_TABLE::OnExport( wxCommandEvent& aEvent )
  976. {
  977. if( m_dataModel->IsEdited() )
  978. {
  979. if( OKOrCancelDialog( nullptr, _( "Unsaved data" ),
  980. _( "Changes have not yet been saved. Export unsaved data?" ), "",
  981. _( "OK" ), _( "Cancel" ) )
  982. == wxID_CANCEL )
  983. {
  984. return;
  985. }
  986. }
  987. // Create output directory if it does not exist (also transform it in absolute form).
  988. // Bail if it fails.
  989. std::function<bool( wxString* )> textResolver =
  990. [&]( wxString* token ) -> bool
  991. {
  992. SCHEMATIC& schematic = m_parent->Schematic();
  993. // Handles m_board->GetTitleBlock() *and* m_board->GetProject()
  994. return schematic.ResolveTextVar( &schematic.CurrentSheet(), token, 0 );
  995. };
  996. wxString path = m_outputFileName->GetValue();
  997. if( path.IsEmpty() )
  998. {
  999. DisplayError( this, _( "No filename specified in exporter" ) );
  1000. return;
  1001. }
  1002. path = ExpandTextVars( path, &textResolver );
  1003. path = ExpandEnvVarSubstitutions( path, nullptr );
  1004. wxFileName outputFile = wxFileName::FileName( path );
  1005. wxString msg;
  1006. if( !EnsureFileDirectoryExists( &outputFile,
  1007. Prj().AbsolutePath( m_parent->Schematic().GetFileName() ),
  1008. &NULL_REPORTER::GetInstance() ) )
  1009. {
  1010. msg.Printf( _( "Could not open/create path '%s'." ), outputFile.GetPath() );
  1011. DisplayError( this, msg );
  1012. return;
  1013. }
  1014. wxFFile out( outputFile.GetFullPath(), "wb" );
  1015. if( !out.IsOpened() )
  1016. {
  1017. msg.Printf( _( "Could not create BOM output '%s'." ), outputFile.GetFullPath() );
  1018. DisplayError( this, msg );
  1019. return;
  1020. }
  1021. PreviewRefresh();
  1022. if( !out.Write( m_textOutput->GetValue() ) )
  1023. {
  1024. msg.Printf( _( "Could not write BOM output '%s'." ), outputFile.GetFullPath() );
  1025. DisplayError( this, msg );
  1026. return;
  1027. }
  1028. out.Close(); // close the file before we tell the user it's done with the info modal :workflow meme:
  1029. msg.Printf( _( "Wrote BOM output to '%s'" ), outputFile.GetFullPath() );
  1030. DisplayInfoMessage( this, msg );
  1031. }
  1032. void DIALOG_SYMBOL_FIELDS_TABLE::OnCancel( wxCommandEvent& aEvent )
  1033. {
  1034. Close();
  1035. }
  1036. void DIALOG_SYMBOL_FIELDS_TABLE::OnOk( wxCommandEvent& aEvent )
  1037. {
  1038. TransferDataFromWindow();
  1039. Close();
  1040. }
  1041. void DIALOG_SYMBOL_FIELDS_TABLE::OnClose( wxCloseEvent& aEvent )
  1042. {
  1043. // This is a cancel, so commit quietly as we're going to throw the results away anyway.
  1044. m_grid->CommitPendingChanges( true );
  1045. if( m_dataModel->IsEdited() )
  1046. {
  1047. if( !HandleUnsavedChanges( this, _( "Save changes?" ),
  1048. [&]() -> bool
  1049. {
  1050. return TransferDataFromWindow();
  1051. } ) )
  1052. {
  1053. aEvent.Veto();
  1054. return;
  1055. }
  1056. }
  1057. // Stop listening to schematic events
  1058. m_parent->Schematic().RemoveListener( this );
  1059. // Save all our settings since we're really closing
  1060. savePresetsToSchematic();
  1061. m_schSettings.m_BomExportFileName = m_outputFileName->GetValue();
  1062. EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
  1063. cfg->m_FieldEditorPanel.width = GetSize().x;
  1064. cfg->m_FieldEditorPanel.height = GetSize().y;
  1065. cfg->m_FieldEditorPanel.page = m_nbPages->GetSelection();
  1066. if( m_radioHighlight->GetValue() )
  1067. cfg->m_FieldEditorPanel.selection_mode = 0;
  1068. else if( m_radioSelect->GetValue() )
  1069. cfg->m_FieldEditorPanel.selection_mode = 1;
  1070. else if( m_radioOff->GetValue() )
  1071. cfg->m_FieldEditorPanel.selection_mode = 2;
  1072. if( m_radioProject->GetValue() )
  1073. cfg->m_FieldEditorPanel.scope = SCOPE::SCOPE_ALL;
  1074. else if( m_radioCurrentSheet->GetValue() )
  1075. cfg->m_FieldEditorPanel.scope = SCOPE::SCOPE_SHEET;
  1076. else if( m_radioRecursive->GetValue() )
  1077. cfg->m_FieldEditorPanel.scope = SCOPE::SCOPE_SHEET_RECURSIVE;
  1078. for( int i = 0; i < m_grid->GetNumberCols(); i++ )
  1079. {
  1080. if( m_grid->IsColShown( i ) )
  1081. {
  1082. std::string fieldName( m_dataModel->GetColFieldName( i ).ToUTF8() );
  1083. cfg->m_FieldEditorPanel.field_widths[fieldName] = m_grid->GetColSize( i );
  1084. }
  1085. }
  1086. m_parent->FocusOnItem( nullptr );
  1087. wxCommandEvent* evt = new wxCommandEvent( EDA_EVT_CLOSE_DIALOG_SYMBOL_FIELDS_TABLE, wxID_ANY );
  1088. wxWindow* parent = GetParent();
  1089. if( parent )
  1090. wxQueueEvent( parent, evt );
  1091. }
  1092. std::vector<BOM_PRESET> DIALOG_SYMBOL_FIELDS_TABLE::GetUserBomPresets() const
  1093. {
  1094. std::vector<BOM_PRESET> ret;
  1095. for( const std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
  1096. {
  1097. if( !pair.second.readOnly )
  1098. ret.emplace_back( pair.second );
  1099. }
  1100. return ret;
  1101. }
  1102. void DIALOG_SYMBOL_FIELDS_TABLE::SetUserBomPresets( std::vector<BOM_PRESET>& aPresetList )
  1103. {
  1104. // Reset to defaults
  1105. loadDefaultBomPresets();
  1106. for( const BOM_PRESET& preset : aPresetList )
  1107. {
  1108. if( m_bomPresets.count( preset.name ) )
  1109. continue;
  1110. m_bomPresets[preset.name] = preset;
  1111. m_bomPresetMRU.Add( preset.name );
  1112. }
  1113. rebuildBomPresetsWidget();
  1114. }
  1115. void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomPreset( const wxString& aPresetName )
  1116. {
  1117. updateBomPresetSelection( aPresetName );
  1118. wxCommandEvent dummy;
  1119. onBomPresetChanged( dummy );
  1120. }
  1121. void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomPreset( const BOM_PRESET& aPreset )
  1122. {
  1123. if( m_bomPresets.count( aPreset.name ) )
  1124. m_currentBomPreset = &m_bomPresets[aPreset.name];
  1125. else
  1126. m_currentBomPreset = nullptr;
  1127. if( m_currentBomPreset && !m_currentBomPreset->readOnly )
  1128. m_lastSelectedBomPreset = m_currentBomPreset;
  1129. else
  1130. m_lastSelectedBomPreset = nullptr;
  1131. updateBomPresetSelection( aPreset.name );
  1132. doApplyBomPreset( aPreset );
  1133. }
  1134. void DIALOG_SYMBOL_FIELDS_TABLE::loadDefaultBomPresets()
  1135. {
  1136. m_bomPresets.clear();
  1137. m_bomPresetMRU.clear();
  1138. // Load the read-only defaults
  1139. for( const BOM_PRESET& preset : BOM_PRESET::BuiltInPresets() )
  1140. {
  1141. m_bomPresets[preset.name] = preset;
  1142. m_bomPresets[preset.name].readOnly = true;
  1143. m_bomPresetMRU.Add( preset.name );
  1144. }
  1145. }
  1146. void DIALOG_SYMBOL_FIELDS_TABLE::rebuildBomPresetsWidget()
  1147. {
  1148. m_cbBomPresets->Clear();
  1149. // Build the layers preset list.
  1150. // By default, the presetAllLayers will be selected
  1151. int idx = 0;
  1152. int default_idx = 0;
  1153. for( std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
  1154. {
  1155. m_cbBomPresets->Append( wxGetTranslation( pair.first ),
  1156. static_cast<void*>( &pair.second ) );
  1157. if( pair.first == BOM_PRESET::DefaultEditing().name )
  1158. default_idx = idx;
  1159. idx++;
  1160. }
  1161. m_cbBomPresets->Append( wxT( "---" ) );
  1162. m_cbBomPresets->Append( _( "Save preset..." ) );
  1163. m_cbBomPresets->Append( _( "Delete preset..." ) );
  1164. // At least the built-in presets should always be present
  1165. wxASSERT( !m_bomPresets.empty() );
  1166. // Default preset: all Boms
  1167. m_cbBomPresets->SetSelection( default_idx );
  1168. m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( default_idx ) );
  1169. }
  1170. void DIALOG_SYMBOL_FIELDS_TABLE::syncBomPresetSelection()
  1171. {
  1172. BOM_PRESET current = m_dataModel->GetBomSettings();
  1173. auto it = std::find_if( m_bomPresets.begin(), m_bomPresets.end(),
  1174. [&]( const std::pair<const wxString, BOM_PRESET>& aPair )
  1175. {
  1176. const BOM_PRESET& preset = aPair.second;
  1177. // Check the simple settings first
  1178. if( !( preset.sortAsc == current.sortAsc
  1179. && preset.filterString == current.filterString
  1180. && preset.groupSymbols == current.groupSymbols
  1181. && preset.excludeDNP == current.excludeDNP
  1182. && preset.includeExcludedFromBOM
  1183. == current.includeExcludedFromBOM ) )
  1184. {
  1185. return false;
  1186. }
  1187. // We should compare preset.name and current.name.
  1188. // unfortunately current.name is empty because
  1189. // m_dataModel->GetBomSettings() does not store the .name member
  1190. // So use sortField member as a (not very efficient) auxiliary filter.
  1191. // sortField can be translated in m_bomPresets list,
  1192. // so current.sortField needs to be translated
  1193. // Probably this not efficient and error prone test should be removed (JPC).
  1194. if( preset.sortField != wxGetTranslation( current.sortField ) )
  1195. return false;
  1196. // Only compare shown or grouped fields
  1197. std::vector<BOM_FIELD> A, B;
  1198. for( const BOM_FIELD& field : preset.fieldsOrdered )
  1199. {
  1200. if( field.show || field.groupBy )
  1201. A.emplace_back( field );
  1202. }
  1203. for( const BOM_FIELD& field : current.fieldsOrdered )
  1204. {
  1205. if( field.show || field.groupBy )
  1206. B.emplace_back( field );
  1207. }
  1208. return A == B;
  1209. } );
  1210. if( it != m_bomPresets.end() )
  1211. {
  1212. // Select the right m_cbBomPresets item.
  1213. // but these items are translated if they are predefined items.
  1214. bool do_translate = it->second.readOnly;
  1215. wxString text = do_translate ? wxGetTranslation( it->first ) : it->first;
  1216. m_cbBomPresets->SetStringSelection( text );
  1217. }
  1218. else
  1219. {
  1220. m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 ); // separator
  1221. }
  1222. m_currentBomPreset = static_cast<BOM_PRESET*>(
  1223. m_cbBomPresets->GetClientData( m_cbBomPresets->GetSelection() ) );
  1224. }
  1225. void DIALOG_SYMBOL_FIELDS_TABLE::updateBomPresetSelection( const wxString& aName )
  1226. {
  1227. // look at m_userBomPresets to know if aName is a read only preset, or a user preset.
  1228. // Read only presets have translated names in UI, so we have to use
  1229. // a translated name in UI selection.
  1230. // But for a user preset name we should search for aName (not translated)
  1231. wxString ui_label = aName;
  1232. for( std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
  1233. {
  1234. if( pair.first != aName )
  1235. continue;
  1236. if( pair.second.readOnly == true )
  1237. ui_label = wxGetTranslation( aName );
  1238. break;
  1239. }
  1240. int idx = m_cbBomPresets->FindString( ui_label );
  1241. if( idx >= 0 && m_cbBomPresets->GetSelection() != idx )
  1242. {
  1243. m_cbBomPresets->SetSelection( idx );
  1244. m_currentBomPreset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( idx ) );
  1245. }
  1246. else if( idx < 0 )
  1247. {
  1248. m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 ); // separator
  1249. }
  1250. }
  1251. void DIALOG_SYMBOL_FIELDS_TABLE::onBomPresetChanged( wxCommandEvent& aEvent )
  1252. {
  1253. int count = m_cbBomPresets->GetCount();
  1254. int index = m_cbBomPresets->GetSelection();
  1255. auto resetSelection =
  1256. [&]()
  1257. {
  1258. if( m_currentBomPreset )
  1259. m_cbBomPresets->SetStringSelection( m_currentBomPreset->name );
  1260. else
  1261. m_cbBomPresets->SetSelection( m_cbBomPresets->GetCount() - 3 );
  1262. };
  1263. if( index == count - 3 )
  1264. {
  1265. // Separator: reject the selection
  1266. resetSelection();
  1267. return;
  1268. }
  1269. else if( index == count - 2 )
  1270. {
  1271. // Save current state to new preset
  1272. wxString name;
  1273. if( m_lastSelectedBomPreset )
  1274. name = m_lastSelectedBomPreset->name;
  1275. wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
  1276. if( dlg.ShowModal() != wxID_OK )
  1277. {
  1278. resetSelection();
  1279. return;
  1280. }
  1281. name = dlg.GetValue();
  1282. bool exists = m_bomPresets.count( name );
  1283. if( !exists )
  1284. {
  1285. m_bomPresets[name] = m_dataModel->GetBomSettings();
  1286. m_bomPresets[name].readOnly = false;
  1287. m_bomPresets[name].name = name;
  1288. }
  1289. BOM_PRESET* preset = &m_bomPresets[name];
  1290. if( !exists )
  1291. {
  1292. index = m_cbBomPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
  1293. }
  1294. else if( preset->readOnly )
  1295. {
  1296. wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
  1297. _( "Error" ), wxOK | wxICON_ERROR, this );
  1298. resetSelection();
  1299. return;
  1300. }
  1301. else
  1302. {
  1303. // Ask the user if they want to overwrite the existing preset
  1304. if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
  1305. {
  1306. resetSelection();
  1307. return;
  1308. }
  1309. *preset = m_dataModel->GetBomSettings();
  1310. preset->name = name;
  1311. index = m_cbBomPresets->FindString( name );
  1312. m_bomPresetMRU.Remove( name );
  1313. }
  1314. m_currentBomPreset = preset;
  1315. m_cbBomPresets->SetSelection( index );
  1316. m_bomPresetMRU.Insert( name, 0 );
  1317. return;
  1318. }
  1319. else if( index == count - 1 )
  1320. {
  1321. // Delete a preset
  1322. wxArrayString headers;
  1323. std::vector<wxArrayString> items;
  1324. headers.Add( _( "Presets" ) );
  1325. for( std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
  1326. {
  1327. if( !pair.second.readOnly )
  1328. {
  1329. wxArrayString item;
  1330. item.Add( pair.first );
  1331. items.emplace_back( item );
  1332. }
  1333. }
  1334. EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
  1335. dlg.SetListLabel( _( "Select preset:" ) );
  1336. if( dlg.ShowModal() == wxID_OK )
  1337. {
  1338. wxString presetName = dlg.GetTextSelection();
  1339. int idx = m_cbBomPresets->FindString( presetName );
  1340. if( idx != wxNOT_FOUND )
  1341. {
  1342. m_bomPresets.erase( presetName );
  1343. m_cbBomPresets->Delete( idx );
  1344. m_currentBomPreset = nullptr;
  1345. m_bomPresetMRU.Remove( presetName );
  1346. }
  1347. }
  1348. resetSelection();
  1349. return;
  1350. }
  1351. BOM_PRESET* preset = static_cast<BOM_PRESET*>( m_cbBomPresets->GetClientData( index ) );
  1352. m_currentBomPreset = preset;
  1353. m_lastSelectedBomPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
  1354. if( preset )
  1355. {
  1356. doApplyBomPreset( *preset );
  1357. syncBomPresetSelection();
  1358. m_currentBomPreset = preset;
  1359. if( !m_currentBomPreset->name.IsEmpty() )
  1360. {
  1361. m_bomPresetMRU.Remove( preset->name );
  1362. m_bomPresetMRU.Insert( preset->name, 0 );
  1363. }
  1364. }
  1365. }
  1366. void DIALOG_SYMBOL_FIELDS_TABLE::doApplyBomPreset( const BOM_PRESET& aPreset )
  1367. {
  1368. // Disable rebuilds while we're applying the preset otherwise we'll be
  1369. // rebuilding the model constantly while firing off wx events
  1370. m_dataModel->DisableRebuilds();
  1371. // Basically, we apply the BOM preset to the data model and then
  1372. // update our UI to reflect resulting the data model state, not the preset.
  1373. m_dataModel->ApplyBomPreset( aPreset );
  1374. // BOM Presets can add, but not remove, columns, so make sure the field control
  1375. // grid has all of them before starting
  1376. for( int i = 0; i < m_dataModel->GetColsCount(); i++ )
  1377. {
  1378. const wxString& fieldName( m_dataModel->GetColFieldName( i ) );
  1379. bool found = false;
  1380. for( int j = 0; j < m_fieldsCtrl->GetItemCount(); j++ )
  1381. {
  1382. if( m_fieldsCtrl->GetTextValue( j, FIELD_NAME_COLUMN ) == fieldName )
  1383. {
  1384. found = true;
  1385. break;
  1386. }
  1387. }
  1388. // Properties like label, etc. will be added in the next loop
  1389. if( !found )
  1390. AddField( fieldName, GetTextVars( fieldName ), false, false );
  1391. }
  1392. // Sync all fields
  1393. for( int i = 0; i < m_fieldsCtrl->GetItemCount(); i++ )
  1394. {
  1395. const wxString& fieldName( m_fieldsCtrl->GetTextValue( i, FIELD_NAME_COLUMN ) );
  1396. int col = m_dataModel->GetFieldNameCol( fieldName );
  1397. if( col == -1 )
  1398. {
  1399. wxASSERT_MSG( true, "Fields control has a field not found in the data model." );
  1400. continue;
  1401. }
  1402. EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
  1403. std::string fieldNameStr( fieldName.ToUTF8() );
  1404. // Set column labels
  1405. const wxString& label = m_dataModel->GetColLabelValue( col );
  1406. m_fieldsCtrl->SetTextValue( label, i, LABEL_COLUMN );
  1407. m_grid->SetColLabelValue( col, label );
  1408. if( cfg->m_FieldEditorPanel.field_widths.count( fieldNameStr ) )
  1409. m_grid->SetColSize( col, cfg->m_FieldEditorPanel.field_widths.at( fieldNameStr ) );
  1410. // Set shown colums
  1411. bool show = m_dataModel->GetShowColumn( col );
  1412. m_fieldsCtrl->SetToggleValue( show, i, SHOW_FIELD_COLUMN );
  1413. if( show )
  1414. m_grid->ShowCol( col );
  1415. else
  1416. m_grid->HideCol( col );
  1417. // Set grouped columns
  1418. bool groupBy = m_dataModel->GetGroupColumn( col );
  1419. m_fieldsCtrl->SetToggleValue( groupBy, i, GROUP_BY_COLUMN );
  1420. }
  1421. m_grid->SetSortingColumn( m_dataModel->GetSortCol(), m_dataModel->GetSortAsc() );
  1422. m_groupSymbolsBox->SetValue( m_dataModel->GetGroupingEnabled() );
  1423. m_filter->ChangeValue( m_dataModel->GetFilter() );
  1424. m_checkExcludeDNP->SetValue( m_dataModel->GetExcludeDNP() );
  1425. m_checkShowExcluded->SetValue( m_dataModel->GetIncludeExcludedFromBOM() );
  1426. SetupAllColumnProperties();
  1427. // This will rebuild all rows and columns in the model such that the order
  1428. // and labels are right, then we refresh the shown grid data to match
  1429. m_dataModel->EnableRebuilds();
  1430. m_dataModel->RebuildRows();
  1431. m_grid->ForceRefresh();
  1432. }
  1433. std::vector<BOM_FMT_PRESET> DIALOG_SYMBOL_FIELDS_TABLE::GetUserBomFmtPresets() const
  1434. {
  1435. std::vector<BOM_FMT_PRESET> ret;
  1436. for( const std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
  1437. {
  1438. if( !pair.second.readOnly )
  1439. ret.emplace_back( pair.second );
  1440. }
  1441. return ret;
  1442. }
  1443. void DIALOG_SYMBOL_FIELDS_TABLE::SetUserBomFmtPresets( std::vector<BOM_FMT_PRESET>& aPresetList )
  1444. {
  1445. // Reset to defaults
  1446. loadDefaultBomFmtPresets();
  1447. for( const BOM_FMT_PRESET& preset : aPresetList )
  1448. {
  1449. if( m_bomFmtPresets.count( preset.name ) )
  1450. continue;
  1451. m_bomFmtPresets[preset.name] = preset;
  1452. m_bomFmtPresetMRU.Add( preset.name );
  1453. }
  1454. rebuildBomFmtPresetsWidget();
  1455. }
  1456. void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomFmtPreset( const wxString& aPresetName )
  1457. {
  1458. updateBomFmtPresetSelection( aPresetName );
  1459. wxCommandEvent dummy;
  1460. onBomFmtPresetChanged( dummy );
  1461. }
  1462. void DIALOG_SYMBOL_FIELDS_TABLE::ApplyBomFmtPreset( const BOM_FMT_PRESET& aPreset )
  1463. {
  1464. if( m_bomFmtPresets.count( aPreset.name ) )
  1465. m_currentBomFmtPreset = &m_bomFmtPresets[aPreset.name];
  1466. else
  1467. m_currentBomFmtPreset = nullptr;
  1468. m_lastSelectedBomFmtPreset = ( m_currentBomFmtPreset
  1469. && !m_currentBomFmtPreset->readOnly ) ? m_currentBomFmtPreset
  1470. : nullptr;
  1471. updateBomFmtPresetSelection( aPreset.name );
  1472. doApplyBomFmtPreset( aPreset );
  1473. }
  1474. void DIALOG_SYMBOL_FIELDS_TABLE::loadDefaultBomFmtPresets()
  1475. {
  1476. m_bomFmtPresets.clear();
  1477. m_bomFmtPresetMRU.clear();
  1478. // Load the read-only defaults
  1479. for( const BOM_FMT_PRESET& preset : BOM_FMT_PRESET::BuiltInPresets() )
  1480. {
  1481. m_bomFmtPresets[preset.name] = preset;
  1482. m_bomFmtPresets[preset.name].readOnly = true;
  1483. m_bomFmtPresetMRU.Add( preset.name );
  1484. }
  1485. }
  1486. void DIALOG_SYMBOL_FIELDS_TABLE::rebuildBomFmtPresetsWidget()
  1487. {
  1488. m_cbBomFmtPresets->Clear();
  1489. // Build the layers preset list.
  1490. // By default, the presetAllLayers will be selected
  1491. int idx = 0;
  1492. int default_idx = 0;
  1493. for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
  1494. {
  1495. m_cbBomFmtPresets->Append( wxGetTranslation( pair.first ),
  1496. static_cast<void*>( &pair.second ) );
  1497. if( pair.first == BOM_FMT_PRESET::CSV().name )
  1498. default_idx = idx;
  1499. idx++;
  1500. }
  1501. m_cbBomFmtPresets->Append( wxT( "---" ) );
  1502. m_cbBomFmtPresets->Append( _( "Save preset..." ) );
  1503. m_cbBomFmtPresets->Append( _( "Delete preset..." ) );
  1504. // At least the built-in presets should always be present
  1505. wxASSERT( !m_bomFmtPresets.empty() );
  1506. // Default preset: all Boms
  1507. m_cbBomFmtPresets->SetSelection( default_idx );
  1508. m_currentBomFmtPreset =
  1509. static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( default_idx ) );
  1510. }
  1511. void DIALOG_SYMBOL_FIELDS_TABLE::syncBomFmtPresetSelection()
  1512. {
  1513. BOM_FMT_PRESET current = GetCurrentBomFmtSettings();
  1514. auto it = std::find_if( m_bomFmtPresets.begin(), m_bomFmtPresets.end(),
  1515. [&]( const std::pair<const wxString, BOM_FMT_PRESET>& aPair )
  1516. {
  1517. return ( aPair.second.fieldDelimiter == current.fieldDelimiter
  1518. && aPair.second.stringDelimiter == current.stringDelimiter
  1519. && aPair.second.refDelimiter == current.refDelimiter
  1520. && aPair.second.refRangeDelimiter == current.refRangeDelimiter
  1521. && aPair.second.keepTabs == current.keepTabs
  1522. && aPair.second.keepLineBreaks == current.keepLineBreaks );
  1523. } );
  1524. if( it != m_bomFmtPresets.end() )
  1525. {
  1526. // Select the right m_cbBomFmtPresets item.
  1527. // but these items are translated if they are predefined items.
  1528. bool do_translate = it->second.readOnly;
  1529. wxString text = do_translate ? wxGetTranslation( it->first ) : it->first;
  1530. m_cbBomFmtPresets->SetStringSelection( text );
  1531. }
  1532. else
  1533. {
  1534. m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 ); // separator
  1535. }
  1536. m_currentBomFmtPreset = static_cast<BOM_FMT_PRESET*>(
  1537. m_cbBomFmtPresets->GetClientData( m_cbBomFmtPresets->GetSelection() ) );
  1538. }
  1539. void DIALOG_SYMBOL_FIELDS_TABLE::updateBomFmtPresetSelection( const wxString& aName )
  1540. {
  1541. // look at m_userBomFmtPresets to know if aName is a read only preset, or a user preset.
  1542. // Read only presets have translated names in UI, so we have to use
  1543. // a translated name in UI selection.
  1544. // But for a user preset name we should search for aName (not translated)
  1545. wxString ui_label = aName;
  1546. for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
  1547. {
  1548. if( pair.first != aName )
  1549. continue;
  1550. if( pair.second.readOnly == true )
  1551. ui_label = wxGetTranslation( aName );
  1552. break;
  1553. }
  1554. int idx = m_cbBomFmtPresets->FindString( ui_label );
  1555. if( idx >= 0 && m_cbBomFmtPresets->GetSelection() != idx )
  1556. {
  1557. m_cbBomFmtPresets->SetSelection( idx );
  1558. m_currentBomFmtPreset =
  1559. static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( idx ) );
  1560. }
  1561. else if( idx < 0 )
  1562. {
  1563. m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 ); // separator
  1564. }
  1565. }
  1566. void DIALOG_SYMBOL_FIELDS_TABLE::onBomFmtPresetChanged( wxCommandEvent& aEvent )
  1567. {
  1568. int count = m_cbBomFmtPresets->GetCount();
  1569. int index = m_cbBomFmtPresets->GetSelection();
  1570. auto resetSelection =
  1571. [&]()
  1572. {
  1573. if( m_currentBomFmtPreset )
  1574. m_cbBomFmtPresets->SetStringSelection( m_currentBomFmtPreset->name );
  1575. else
  1576. m_cbBomFmtPresets->SetSelection( m_cbBomFmtPresets->GetCount() - 3 );
  1577. };
  1578. if( index == count - 3 )
  1579. {
  1580. // Separator: reject the selection
  1581. resetSelection();
  1582. return;
  1583. }
  1584. else if( index == count - 2 )
  1585. {
  1586. // Save current state to new preset
  1587. wxString name;
  1588. if( m_lastSelectedBomFmtPreset )
  1589. name = m_lastSelectedBomFmtPreset->name;
  1590. wxTextEntryDialog dlg( this, _( "BOM preset name:" ), _( "Save BOM Preset" ), name );
  1591. if( dlg.ShowModal() != wxID_OK )
  1592. {
  1593. resetSelection();
  1594. return;
  1595. }
  1596. name = dlg.GetValue();
  1597. bool exists = m_bomFmtPresets.count( name );
  1598. if( !exists )
  1599. {
  1600. m_bomFmtPresets[name] = GetCurrentBomFmtSettings();
  1601. m_bomFmtPresets[name].readOnly = false;
  1602. m_bomFmtPresets[name].name = name;
  1603. }
  1604. BOM_FMT_PRESET* preset = &m_bomFmtPresets[name];
  1605. if( !exists )
  1606. {
  1607. index = m_cbBomFmtPresets->Insert( name, index - 1, static_cast<void*>( preset ) );
  1608. }
  1609. else if( preset->readOnly )
  1610. {
  1611. wxMessageBox( _( "Default presets cannot be modified.\nPlease use a different name." ),
  1612. _( "Error" ), wxOK | wxICON_ERROR, this );
  1613. resetSelection();
  1614. return;
  1615. }
  1616. else
  1617. {
  1618. // Ask the user if they want to overwrite the existing preset
  1619. if( !IsOK( this, _( "Overwrite existing preset?" ) ) )
  1620. {
  1621. resetSelection();
  1622. return;
  1623. }
  1624. *preset = GetCurrentBomFmtSettings();
  1625. preset->name = name;
  1626. index = m_cbBomFmtPresets->FindString( name );
  1627. m_bomFmtPresetMRU.Remove( name );
  1628. }
  1629. m_currentBomFmtPreset = preset;
  1630. m_cbBomFmtPresets->SetSelection( index );
  1631. m_bomFmtPresetMRU.Insert( name, 0 );
  1632. return;
  1633. }
  1634. else if( index == count - 1 )
  1635. {
  1636. // Delete a preset
  1637. wxArrayString headers;
  1638. std::vector<wxArrayString> items;
  1639. headers.Add( _( "Presets" ) );
  1640. for( std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
  1641. {
  1642. if( !pair.second.readOnly )
  1643. {
  1644. wxArrayString item;
  1645. item.Add( pair.first );
  1646. items.emplace_back( item );
  1647. }
  1648. }
  1649. EDA_LIST_DIALOG dlg( this, _( "Delete Preset" ), headers, items );
  1650. dlg.SetListLabel( _( "Select preset:" ) );
  1651. if( dlg.ShowModal() == wxID_OK )
  1652. {
  1653. wxString presetName = dlg.GetTextSelection();
  1654. int idx = m_cbBomFmtPresets->FindString( presetName );
  1655. if( idx != wxNOT_FOUND )
  1656. {
  1657. m_bomFmtPresets.erase( presetName );
  1658. m_cbBomFmtPresets->Delete( idx );
  1659. m_currentBomFmtPreset = nullptr;
  1660. m_bomFmtPresetMRU.Remove( presetName );
  1661. }
  1662. }
  1663. resetSelection();
  1664. return;
  1665. }
  1666. auto* preset = static_cast<BOM_FMT_PRESET*>( m_cbBomFmtPresets->GetClientData( index ) );
  1667. m_currentBomFmtPreset = preset;
  1668. m_lastSelectedBomFmtPreset = ( !preset || preset->readOnly ) ? nullptr : preset;
  1669. if( preset )
  1670. {
  1671. doApplyBomFmtPreset( *preset );
  1672. syncBomFmtPresetSelection();
  1673. m_currentBomFmtPreset = preset;
  1674. if( !m_currentBomFmtPreset->name.IsEmpty() )
  1675. {
  1676. m_bomFmtPresetMRU.Remove( preset->name );
  1677. m_bomFmtPresetMRU.Insert( preset->name, 0 );
  1678. }
  1679. }
  1680. }
  1681. void DIALOG_SYMBOL_FIELDS_TABLE::doApplyBomFmtPreset( const BOM_FMT_PRESET& aPreset )
  1682. {
  1683. m_textFieldDelimiter->ChangeValue( aPreset.fieldDelimiter );
  1684. m_textStringDelimiter->ChangeValue( aPreset.stringDelimiter );
  1685. m_textRefDelimiter->ChangeValue( aPreset.refDelimiter );
  1686. m_textRefRangeDelimiter->ChangeValue( aPreset.refRangeDelimiter );
  1687. m_checkKeepTabs->SetValue( aPreset.keepTabs );
  1688. m_checkKeepLineBreaks->SetValue( aPreset.keepLineBreaks );
  1689. // Refresh the preview if that's the current page
  1690. if( m_nbPages->GetSelection() == 1 )
  1691. PreviewRefresh();
  1692. }
  1693. void DIALOG_SYMBOL_FIELDS_TABLE::savePresetsToSchematic()
  1694. {
  1695. bool modified = false;
  1696. // Save our BOM presets
  1697. std::vector<BOM_PRESET> presets;
  1698. for( const std::pair<const wxString, BOM_PRESET>& pair : m_bomPresets )
  1699. {
  1700. if( !pair.second.readOnly )
  1701. presets.emplace_back( pair.second );
  1702. }
  1703. if( m_schSettings.m_BomPresets != presets )
  1704. {
  1705. modified = true;
  1706. m_schSettings.m_BomPresets = presets;
  1707. }
  1708. if( m_schSettings.m_BomSettings != m_dataModel->GetBomSettings() )
  1709. {
  1710. modified = true;
  1711. m_schSettings.m_BomSettings = m_dataModel->GetBomSettings();
  1712. }
  1713. // Save our BOM Format presets
  1714. std::vector<BOM_FMT_PRESET> fmts;
  1715. for( const std::pair<const wxString, BOM_FMT_PRESET>& pair : m_bomFmtPresets )
  1716. {
  1717. if( !pair.second.readOnly )
  1718. fmts.emplace_back( pair.second );
  1719. }
  1720. if( m_schSettings.m_BomFmtPresets != fmts )
  1721. {
  1722. modified = true;
  1723. m_schSettings.m_BomFmtPresets = fmts;
  1724. }
  1725. if( m_schSettings.m_BomFmtSettings != GetCurrentBomFmtSettings() )
  1726. {
  1727. modified = true;
  1728. m_schSettings.m_BomFmtSettings = GetCurrentBomFmtSettings();
  1729. }
  1730. if( modified )
  1731. m_parent->OnModify();
  1732. }
  1733. void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsAdded( SCHEMATIC& aSch,
  1734. std::vector<SCH_ITEM*>& aSchItem )
  1735. {
  1736. SCH_REFERENCE_LIST allRefs;
  1737. m_parent->Schematic().BuildUnorderedSheetList().GetSymbols( allRefs );
  1738. for( SCH_ITEM* item : aSchItem )
  1739. {
  1740. if( item->Type() == SCH_SYMBOL_T )
  1741. {
  1742. SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
  1743. // Don't add power symbols
  1744. if( !symbol->IsMissingLibSymbol() && symbol->IsPower() )
  1745. continue;
  1746. // Add all fields again in case this symbol has a new one
  1747. for( SCH_FIELD& field : symbol->GetFields() )
  1748. AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
  1749. m_dataModel->AddReferences( getSymbolReferences( symbol, allRefs ) );
  1750. }
  1751. else if( item->Type() == SCH_SHEET_T )
  1752. {
  1753. std::set<SCH_SYMBOL*> symbols;
  1754. SCH_REFERENCE_LIST refs = getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) );
  1755. for( SCH_REFERENCE& ref : refs )
  1756. symbols.insert( ref.GetSymbol() );
  1757. for( SCH_SYMBOL* symbol : symbols )
  1758. {
  1759. // Add all fields again in case this symbol has a new one
  1760. for( SCH_FIELD& field : symbol->GetFields() )
  1761. AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
  1762. }
  1763. m_dataModel->AddReferences( refs );
  1764. }
  1765. }
  1766. DisableSelectionEvents();
  1767. m_dataModel->RebuildRows();
  1768. EnableSelectionEvents();
  1769. }
  1770. void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsRemoved( SCHEMATIC& aSch,
  1771. std::vector<SCH_ITEM*>& aSchItem )
  1772. {
  1773. for( SCH_ITEM* item : aSchItem )
  1774. {
  1775. if( item->Type() == SCH_SYMBOL_T )
  1776. {
  1777. m_dataModel->RemoveSymbol( *static_cast<SCH_SYMBOL*>( item ) );
  1778. }
  1779. else if( item->Type() == SCH_SHEET_T )
  1780. {
  1781. m_dataModel->RemoveReferences(
  1782. getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) ) );
  1783. }
  1784. }
  1785. DisableSelectionEvents();
  1786. m_dataModel->RebuildRows();
  1787. EnableSelectionEvents();
  1788. }
  1789. void DIALOG_SYMBOL_FIELDS_TABLE::OnSchItemsChanged( SCHEMATIC& aSch,
  1790. std::vector<SCH_ITEM*>& aSchItem )
  1791. {
  1792. SCH_REFERENCE_LIST allRefs;
  1793. m_parent->Schematic().BuildUnorderedSheetList().GetSymbols( allRefs );
  1794. for( SCH_ITEM* item : aSchItem )
  1795. {
  1796. if( item->Type() == SCH_SYMBOL_T )
  1797. {
  1798. SCH_SYMBOL* symbol = static_cast<SCH_SYMBOL*>( item );
  1799. // Don't add power symbols
  1800. if( !symbol->IsMissingLibSymbol() && symbol->IsPower() )
  1801. continue;
  1802. // Add all fields again in case this symbol has a new one
  1803. for( SCH_FIELD& field : symbol->GetFields() )
  1804. AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
  1805. m_dataModel->UpdateReferences( getSymbolReferences( symbol, allRefs ) );
  1806. }
  1807. else if( item->Type() == SCH_SHEET_T )
  1808. {
  1809. std::set<SCH_SYMBOL*> symbols;
  1810. SCH_REFERENCE_LIST refs = getSheetSymbolReferences( *static_cast<SCH_SHEET*>( item ) );
  1811. for( SCH_REFERENCE& ref : refs )
  1812. symbols.insert( ref.GetSymbol() );
  1813. for( SCH_SYMBOL* symbol : symbols )
  1814. {
  1815. // Add all fields again in case this symbol has a new one
  1816. for( SCH_FIELD& field : symbol->GetFields() )
  1817. AddField( field.GetCanonicalName(), field.GetName(), true, false, true );
  1818. }
  1819. m_dataModel->UpdateReferences( refs );
  1820. }
  1821. }
  1822. DisableSelectionEvents();
  1823. m_dataModel->RebuildRows();
  1824. EnableSelectionEvents();
  1825. }
  1826. void DIALOG_SYMBOL_FIELDS_TABLE::OnSchSheetChanged( SCHEMATIC& aSch )
  1827. {
  1828. m_dataModel->SetPath( aSch.CurrentSheet() );
  1829. if( m_dataModel->GetScope() != FIELDS_EDITOR_GRID_DATA_MODEL::SCOPE::SCOPE_ALL )
  1830. {
  1831. DisableSelectionEvents();
  1832. m_dataModel->RebuildRows();
  1833. EnableSelectionEvents();
  1834. }
  1835. }
  1836. void DIALOG_SYMBOL_FIELDS_TABLE::EnableSelectionEvents()
  1837. {
  1838. m_grid->Connect(
  1839. wxEVT_GRID_RANGE_SELECTED,
  1840. wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected ),
  1841. nullptr, this );
  1842. }
  1843. void DIALOG_SYMBOL_FIELDS_TABLE::DisableSelectionEvents()
  1844. {
  1845. m_grid->Disconnect(
  1846. wxEVT_GRID_RANGE_SELECTED,
  1847. wxGridRangeSelectEventHandler( DIALOG_SYMBOL_FIELDS_TABLE::OnTableRangeSelected ),
  1848. nullptr, this );
  1849. }
  1850. SCH_REFERENCE_LIST
  1851. DIALOG_SYMBOL_FIELDS_TABLE::getSymbolReferences( SCH_SYMBOL* aSymbol,
  1852. SCH_REFERENCE_LIST& aCachedRefs )
  1853. {
  1854. SCH_REFERENCE_LIST symbolRefs;
  1855. for( size_t i = 0; i < aCachedRefs.GetCount(); i++ )
  1856. {
  1857. SCH_REFERENCE& ref = aCachedRefs[i];
  1858. if( ref.GetSymbol() == aSymbol )
  1859. {
  1860. ref.Split(); // Figures out if we are annotated or not
  1861. symbolRefs.AddItem( ref );
  1862. }
  1863. }
  1864. return symbolRefs;
  1865. }
  1866. SCH_REFERENCE_LIST DIALOG_SYMBOL_FIELDS_TABLE::getSheetSymbolReferences( SCH_SHEET& aSheet )
  1867. {
  1868. SCH_SHEET_LIST allSheets = m_parent->Schematic().BuildUnorderedSheetList();
  1869. SCH_REFERENCE_LIST sheetRefs;
  1870. // We need to operate on all instances of the sheet
  1871. for( const SCH_SHEET_INSTANCE& instance : aSheet.GetInstances() )
  1872. {
  1873. // For every sheet instance we need to get the current schematic sheet
  1874. // instance that matches that particular sheet path from the root
  1875. for( SCH_SHEET_PATH& basePath : allSheets )
  1876. {
  1877. if( basePath.Path() == instance.m_Path )
  1878. {
  1879. SCH_SHEET_PATH sheetPath = basePath;
  1880. sheetPath.push_back( &aSheet );
  1881. // Create a list of all sheets in this path, starting with the path
  1882. // of the sheet that we just deleted, then all of its subsheets
  1883. SCH_SHEET_LIST subSheets;
  1884. subSheets.push_back( sheetPath );
  1885. allSheets.GetSheetsWithinPath( subSheets, sheetPath );
  1886. subSheets.GetSymbolsWithinPath( sheetRefs, sheetPath, false, false );
  1887. break;
  1888. }
  1889. }
  1890. }
  1891. for( SCH_REFERENCE& ref : sheetRefs )
  1892. ref.Split();
  1893. return sheetRefs;
  1894. }