KiCad PCB EDA Suite
dialog_bom.cpp
Go to the documentation of this file.
1 /*
2  * This program source code file is part of KiCad, a free EDA CAD application.
3  *
4  * Copyright (C) 2019 Jean-Pierre Charras, jp.charras at wanadoo.fr
5  * Copyright (C) 1992-2021 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 
31 #include <bitmaps.h>
32 #include <bom_plugins.h>
33 #include <confirm.h>
34 #include <dialog_bom_base.h>
35 #include <dialog_helpers.h>
36 #include <eeschema_settings.h>
37 #include <gestfich.h>
39 #include <i18n_utility.h> // for _HKI definition used in dialog_bom_help_md.h
40 #include <invoke_sch_dialog.h>
41 #include <kiface_i.h>
42 #include <netlist_exporter_xml.h>
43 #include <pgm_base.h>
44 #include <reporter.h>
45 #include <sch_edit_frame.h>
46 #include <paths.h>
47 
48 #include <wx/filedlg.h>
49 #include <wx/log.h>
50 #include <wx/textdlg.h>
51 
52 wxString s_bomHelpInfo =
53 #include <dialog_bom_help_md.h>
54 ;
55 
56 // BOM "plugins" are not actually plugins. They are external tools
57 // (scripts or executables) called by this dialog.
58 typedef std::vector<BOM_GENERATOR_HANDLER::PTR> BOM_GENERATOR_ARRAY;
59 
60 
61 // The main dialog frame to run scripts to build bom
63 {
64 private:
68 
70 
71 public:
72  DIALOG_BOM( SCH_EDIT_FRAME* parent );
73  ~DIALOG_BOM();
74 
75 private:
76  void OnGeneratorSelected( wxCommandEvent& event ) override;
77  void OnRunGenerator( wxCommandEvent& event ) override;
78  void OnHelp( wxCommandEvent& event ) override;
79  void OnAddGenerator( wxCommandEvent& event ) override;
80  void OnRemoveGenerator( wxCommandEvent& event ) override;
81  void OnEditGenerator( wxCommandEvent& event ) override;
82  void OnCommandLineEdited( wxCommandEvent& event ) override;
83  void OnNameEdited( wxCommandEvent& event ) override;
84  void OnShowConsoleChanged( wxCommandEvent& event ) override;
85  void OnIdle( wxIdleEvent& event ) override;
86 
87  void pluginInit();
88  void installGeneratorsList();
89  BOM_GENERATOR_HANDLER* addGenerator( const wxString& aPath,
90  const wxString& aName = wxEmptyString );
91  bool pluginExists( const wxString& aName );
92 
94  {
95  int idx = m_lbGenerators->GetSelection();
96 
97  if( idx < 0 || idx >= (int)m_generators.size() )
98  return nullptr;
99 
100  return m_generators[idx].get();
101  }
102 
103  wxString chooseGenerator();
104 };
105 
106 
107 // Create and show DIALOG_BOM.
109 {
110  DIALOG_BOM dlg( aCaller );
111 
112  // QuasiModal so syntax help works
113  return dlg.ShowQuasiModal();
114 }
115 
116 
118  DIALOG_BOM_BASE( parent ),
119  m_parent( parent ),
120  m_initialized( false ),
121  m_helpWindow( nullptr )
122 {
125  m_buttonEdit->SetBitmap( KiBitmap( BITMAPS::small_edit ) );
126 
128 
129 #ifndef __WINDOWS__
130  m_checkBoxShowConsole->Show( false );
131 #endif
132 
133  m_sdbSizerOK->SetLabel( _( "Generate" ) );
134  m_sdbSizerCancel->SetLabel( _( "Close" ) );
135  m_sdbSizer->Layout();
136 
138  m_sdbSizerOK->SetDefault();
139 
140  // Now all widgets have the size fixed, call FinishDialogSettings
142 
143  m_buttonReset->Bind( wxEVT_BUTTON,
144  [&]( wxCommandEvent& )
145  {
147 
148  cfg->m_BomPanel.selected_plugin = wxEmptyString;
149  cfg->m_BomPanel.plugins = cfg->DefaultBomPlugins();
150 
152  } );
153 }
154 
155 
157 {
158  if( m_helpWindow )
159  m_helpWindow->Destroy();
160 
162 
163  cfg->m_BomPanel.plugins.clear();
164 
165  for( const std::unique_ptr<BOM_GENERATOR_HANDLER>& plugin : m_generators )
166  {
167  wxString name = plugin->GetName();
168  wxFileName path( plugin->GetStoredPath() );
169 
170  // handle empty nickname by stripping path
171  if( name.IsEmpty() )
172  name = path.GetName();
173 
174  EESCHEMA_SETTINGS::BOM_PLUGIN_SETTINGS setting( name, path.GetFullPath() );
175  setting.command = plugin->GetCommand();
176 
177  cfg->m_BomPanel.plugins.emplace_back( setting );
178  }
179 
180  cfg->m_BomPanel.selected_plugin = m_lbGenerators->GetStringSelection().ToStdString();
181 }
182 
183 
184 // Read the initialized plugins in config and fill the list of names
186 {
188 
189  wxString active_plugin_name = cfg->m_BomPanel.selected_plugin;
190 
191  m_generators.clear();
192 
194  {
195  auto plugin = std::make_unique<BOM_GENERATOR_HANDLER>( setting.path );
196 
197  plugin->SetName( setting.name );
198 
199  if( !setting.command.IsEmpty() )
200  plugin->SetCommand( setting.command );
201 
202  m_generators.emplace_back( std::move( plugin ) );
203  }
204 
205  m_lbGenerators->Clear();
206 
207  if( !m_generators.empty() )
208  {
209  for( unsigned ii = 0; ii < m_generators.size(); ii++ )
210  {
211  wxString name = m_generators[ii]->GetName();
212 
213  if( !m_generators[ii]->FindFilePath().Exists( wxFILE_EXISTS_REGULAR ) )
214  {
215  wxLogTrace( BOM_TRACE, "BOM plugin %s not found",
216  m_generators[ii]->FindFilePath().GetFullName() );
217  name.Append( wxT( " " ) + _( "(file missing)" ) );
218 
219  if( active_plugin_name == name )
220  active_plugin_name.Clear();
221  }
222 
223  m_lbGenerators->Append( name );
224 
225  if( active_plugin_name == name )
226  m_lbGenerators->SetSelection( ii );
227  }
228  }
229 
230  pluginInit();
231 }
232 
233 
234 BOM_GENERATOR_HANDLER* DIALOG_BOM::addGenerator( const wxString& aPath, const wxString& aName )
235 {
236  BOM_GENERATOR_HANDLER* ret = nullptr;
237  auto plugin = std::make_unique<BOM_GENERATOR_HANDLER>( aPath );
238 
239  if( !plugin )
240  return nullptr;
241 
242  if( !aName.IsEmpty() )
243  {
244  plugin->SetName( aName );
245  m_lbGenerators->Append( aName );
246  }
247  else
248  {
249  m_lbGenerators->Append( plugin->GetName() );
250  }
251 
252  ret = plugin.get();
253  m_generators.push_back( std::move( plugin ) );
254  return ret;
255 }
256 
257 
258 bool DIALOG_BOM::pluginExists( const wxString& aName )
259 {
260  for( unsigned ii = 0; ii < m_generators.size(); ii++ )
261  {
262  if( aName == m_generators[ii]->GetName() )
263  return true;
264  }
265 
266  return false;
267 }
268 
269 
270 void DIALOG_BOM::OnGeneratorSelected( wxCommandEvent& event )
271 {
272  pluginInit();
273 }
274 
275 
277 {
279 
280  if( !plugin )
281  {
282  m_textCtrlName->SetValue( wxEmptyString );
283  m_textCtrlCommand->SetValue( wxEmptyString );
284  m_Messages->SetValue( wxEmptyString );
285  return;
286  }
287 
288  if( !plugin->FindFilePath().Exists( wxFILE_EXISTS_REGULAR ) )
289  {
290  m_textCtrlName->SetValue( wxEmptyString );
291  m_textCtrlCommand->SetValue( wxEmptyString );
292 
293  wxString msg =
294  wxString::Format( _( "The selected BOM generator script %s could not be found." ),
295  plugin->GetFile().GetFullPath() );
296 
297  if( !plugin->GetFile().IsAbsolute() )
298  {
299  msg.Append( wxString::Format( _( "\n\nSearched:\n\t%s\n\t%s" ),
302  }
303 
304  m_Messages->SetValue( msg );
305  return;
306  }
307 
308  m_textCtrlName->SetValue( plugin->GetName() );
309  m_textCtrlCommand->SetValue( plugin->GetCommand() );
310  m_Messages->SetValue( plugin->GetInfo() );
311  m_Messages->SetSelection( 0, 0 );
312 
313 #ifdef __WINDOWS__
314  if( plugin->Options().Index( wxT( "show_console" ) ) == wxNOT_FOUND )
315  m_checkBoxShowConsole->SetValue( false );
316  else
317  m_checkBoxShowConsole->SetValue( true );
318 #endif
319 
320  // A plugin can be not working, so do not left the OK button enabled if
321  // the plugin is not ready to use
322  m_sdbSizerOK->Enable( plugin->IsOk() );
323 }
324 
325 
326 void DIALOG_BOM::OnRunGenerator( wxCommandEvent& event )
327 {
328  // Calculate the xml netlist filename
329  wxFileName fn = m_parent->Schematic().GetFileName();
330 
331  fn.ClearExt();
332 
333  wxString fullfilename = fn.GetFullPath();
335 
336  wxString reportmsg;
337  WX_STRING_REPORTER reporter( &reportmsg );
339 
340 #ifdef __WINDOWS__
341  if( m_checkBoxShowConsole->IsChecked() )
342  m_parent->SetExecFlags( wxEXEC_SHOW_CONSOLE );
343 #endif
344 
345  if( m_parent->ReadyToNetlist( _( "Generating BOM requires a fully annotated schematic." ) ) )
346  m_parent->WriteNetListFile( -1, fullfilename, GNL_OPT_BOM, &reporter );
347 
348  m_Messages->SetValue( reportmsg );
349 
350  // Force focus back on the dialog
351  SetFocus();
352 }
353 
354 
355 void DIALOG_BOM::OnRemoveGenerator( wxCommandEvent& event )
356 {
357  int ii = m_lbGenerators->GetSelection();
358 
359  if( ii < 0 )
360  return;
361 
362  m_lbGenerators->Delete( ii );
363  m_generators.erase( m_generators.begin() + ii );
364 
365  // Select the next item, if exists
366  if( m_lbGenerators->GetCount() )
367  m_lbGenerators->SetSelection( std::min( ii, (int) m_lbGenerators->GetCount() - 1 ) );
368 
369  pluginInit();
370 }
371 
372 
373 void DIALOG_BOM::OnAddGenerator( wxCommandEvent& event )
374 {
375  wxString filename = chooseGenerator();
376 
377  if( filename.IsEmpty() )
378  return;
379 
380  // Creates a new plugin entry
381  wxFileName fn( filename );
382  wxString name = wxGetTextFromUser( _( "Generator nickname:" ), _( "Add Generator" ),
383  fn.GetName(), this );
384 
385  if( name.IsEmpty() )
386  return;
387 
388  // Verify if it does not exists
389  if( pluginExists( name ) )
390  {
391  wxMessageBox( wxString::Format( _( "Nickname \"%s\" already in use." ), name ) );
392  return;
393  }
394 
395  try
396  {
397  auto plugin = addGenerator( fn.GetFullPath(), name );
398 
399  if( plugin )
400  {
401  m_lbGenerators->SetSelection( m_lbGenerators->GetCount() - 1 );
402  m_textCtrlCommand->SetValue( plugin->GetCommand() );
403  pluginInit();
404  }
405  }
406  catch( const std::runtime_error& e )
407  {
408  DisplayError( this, e.what() );
409  }
410 }
411 
412 
414 {
415  static wxString lastPath;
416 
417  if( lastPath.IsEmpty() )
418  lastPath = PATHS::GetUserPluginsPath();
419 
420  wxString fullFileName = EDA_FILE_SELECTOR( _( "Generator files:" ), lastPath, wxEmptyString,
421  wxEmptyString, wxFileSelectorDefaultWildcardStr,
422  this, wxFD_OPEN, true );
423 
424  return fullFileName;
425 }
426 
427 
428 void DIALOG_BOM::OnEditGenerator( wxCommandEvent& event )
429 {
430  auto plugin = selectedGenerator();
431 
432  if( !plugin )
433  return;
434 
435  wxString pluginFile = plugin->GetFile().GetFullPath();
436 
437  if( pluginFile.Length() <= 2 ) // if name != ""
438  {
439  wxMessageBox( _( "Generator file name not found." ) );
440  return;
441  }
442 
443  AddDelimiterString( pluginFile );
444  wxString editorname = Pgm().GetEditorName();
445 
446  if( !editorname.IsEmpty() )
447  ExecuteFile( this, editorname, pluginFile );
448  else
449  wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
450 }
451 
452 
453 void DIALOG_BOM::OnHelp( wxCommandEvent& event )
454 {
455  if( m_helpWindow )
456  {
458  return;
459  }
460 
461  m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Bill of Material Generation Help" ) );
462  m_helpWindow->SetDialogSizeInDU( 500, 350 );
463 
464  wxString html_txt;
465  ConvertMarkdown2Html( wxGetTranslation( s_bomHelpInfo ), html_txt );
466 
467  m_helpWindow->m_htmlWindow->AppendToPage( html_txt );
469 }
470 
471 
472 void DIALOG_BOM::OnCommandLineEdited( wxCommandEvent& event )
473 {
474  auto generator = selectedGenerator();
475 
476  if( generator )
477  generator->SetCommand( m_textCtrlCommand->GetValue() );
478 }
479 
480 
481 void DIALOG_BOM::OnNameEdited( wxCommandEvent& event )
482 {
483  if( m_textCtrlName->GetValue().IsEmpty() )
484  return;
485 
486  int ii = m_lbGenerators->GetSelection();
487 
488  if( ii < 0 )
489  return;
490 
491  m_generators[ii]->SetName( m_textCtrlName->GetValue() );
492  m_lbGenerators->SetString( ii, m_generators[ii]->GetName() );
493 }
494 
495 
496 void DIALOG_BOM::OnShowConsoleChanged( wxCommandEvent& event )
497 {
498 #ifdef __WINDOWS__
499  static constexpr wxChar OPT_SHOW_CONSOLE[] = wxT( "show_console" );
500 
501  auto plugin = selectedGenerator();
502 
503  if( !plugin )
504  return;
505 
506  if( m_checkBoxShowConsole->IsChecked() )
507  {
508  if( plugin->Options().Index( OPT_SHOW_CONSOLE ) == wxNOT_FOUND )
509  plugin->Options().Add( OPT_SHOW_CONSOLE );
510  }
511  else
512  {
513  plugin->Options().Remove( OPT_SHOW_CONSOLE );
514  }
515 #endif
516 }
517 
518 
519 void DIALOG_BOM::OnIdle( wxIdleEvent& event )
520 {
521  // On some platforms we initialize wxTextCtrls to all-selected, but we don't want that
522  // for the messages text box.
523  if( !m_initialized )
524  {
525  m_Messages->SetSelection( 0, 0 );
526  m_initialized = true;
527  }
528 }
bool ReadyToNetlist(const wxString &aAnnotateMessage)
Check if we are ready to write a netlist file for the current schematic.
void DisplayError(wxWindow *aParent, const wxString &aText, int aDisplayTime)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:252
wxBitmapButton * m_buttonEdit
wxBitmapButton * m_buttonAddGenerator
void OnCommandLineEdited(wxCommandEvent &event) override
Definition: dialog_bom.cpp:472
void OnEditGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:428
wxStdDialogButtonSizer * m_sdbSizer
wxArrayString & Options()
Accessor to array of options.
Definition: bom_plugins.h:132
This file is part of the common library TODO brief description.
DIALOG_BOM(SCH_EDIT_FRAME *parent)
Definition: dialog_bom.cpp:117
This file is part of the common library.
void ConvertMarkdown2Html(const wxString &aMarkdownInput, wxString &aHtmlOutput)
void OnRemoveGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:355
std::vector< BOM_GENERATOR_HANDLER::PTR > BOM_GENERATOR_ARRAY
Definition: dialog_bom.cpp:58
const wxFileName & GetFile() const
Return the file name of the plugin.
Definition: bom_plugins.h:78
std::vector< BOM_PLUGIN_SETTINGS > plugins
const wxString & GetInfo() const
Return plugin description stored in the plugin header file (if available).
Definition: bom_plugins.h:70
bool pluginExists(const wxString &aName)
Definition: dialog_bom.cpp:258
Schematic editor (Eeschema) main window.
int InvokeDialogCreateBOM(SCH_EDIT_FRAME *aCaller)
Create and show DIALOG_BOM and return whatever DIALOG_BOM::ShowModal() returns.
Definition: dialog_bom.cpp:108
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:106
bool IsOk()
Return true if the plugin is ready to work, i.e.
Definition: bom_plugins.h:58
void SetInitialFocus(wxWindow *aWindow)
Sets the window (usually a wxTextCtrl) that should be focused when the dialog is shown.
Definition: dialog_shim.h:97
void OnIdle(wxIdleEvent &event) override
Definition: dialog_bom.cpp:519
void OnGeneratorSelected(wxCommandEvent &event) override
Definition: dialog_bom.cpp:270
void OnHelp(wxCommandEvent &event) override
Definition: dialog_bom.cpp:453
const wxChar BOM_TRACE[]
Definition: bom_plugins.cpp:32
wxButton * m_sdbSizerOK
EESCHEMA_SETTINGS * eeconfig() const
wxButton * m_buttonReset
static wxString GetUserPluginsPath()
Gets the user path for plugins.
Definition: paths.cpp:53
wxTextCtrl * m_textCtrlName
void OnRunGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:326
wxCheckBox * m_checkBoxShowConsole
void OnNameEdited(wxCommandEvent &event) override
Definition: dialog_bom.cpp:481
Bill of material output generator.
Definition: bom_plugins.h:45
wxFileName FindFilePath() const
Returns the calculated path to the plugin: if the path is already absolute and exists,...
bool WriteNetListFile(int aFormat, const wxString &aFullFileName, unsigned aNetlistOptions, REPORTER *aReporter=nullptr)
Create a netlist file.
int ShowQuasiModal()
wxString EDA_FILE_SELECTOR(const wxString &aTitle, const wxString &aPath, const wxString &aFileName, const wxString &aExtension, const wxString &aWildcard, wxWindow *aParent, int aStyle, const bool aKeepWorkingDirectory, const wxPoint &aPosition, wxString *aMruPath)
A helper function that wraps a call to wxFileSelector.
Definition: gestfich.cpp:52
wxTextCtrl * m_textCtrlCommand
SCHEMATIC & Schematic() const
Helper dialog and control classes.
#define _(s)
void AddDelimiterString(wxString &string)
Add un " to the start and the end of string (if not already done).
Definition: gestfich.cpp:42
wxString GetFileName() const override
Helper to retrieve the filename from the root sheet screen.
Definition: schematic.cpp:123
void installGeneratorsList()
Definition: dialog_bom.cpp:185
HTML_MESSAGE_BOX.
wxBitmap KiBitmap(BITMAPS aBitmap, int aHeightTag)
Construct a wxBitmap from an image identifier Returns the image from the active theme if the image ha...
Definition: bitmap.cpp:105
virtual void ClearMsgPanel()
Clear all messages from the message panel.
wxTextCtrl * m_Messages
bool m_initialized
Definition: dialog_bom.cpp:67
wxString chooseGenerator()
Definition: dialog_bom.cpp:413
BOM_GENERATOR_ARRAY m_generators
Definition: dialog_bom.cpp:66
A wrapper for reporting to a wxString object.
Definition: reporter.h:159
Some functions to handle hotkeys in KiCad.
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, const CPTREE &aTree)
Output a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:200
void ShowModeless()
Show a modeless version of the dialog (without an OK button).
void SetDialogSizeInDU(int aWidth, int aHeight)
set the dialog size, using a "logical" value.
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
void OnAddGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:373
see class PGM_BASE
const char * name
Definition: DXF_plotter.cpp:59
Class DIALOG_BOM_BASE.
const wxString & GetCommand() const
Return the command to execute the plugin.
Definition: bom_plugins.h:116
wxListBox * m_lbGenerators
wxButton * m_sdbSizerCancel
SCH_EDIT_FRAME * m_parent
Definition: dialog_bom.cpp:65
BOM_GENERATOR_HANDLER * selectedGenerator()
Definition: dialog_bom.cpp:93
int ExecuteFile(wxWindow *frame, const wxString &ExecFile, const wxString &param, wxProcess *callback)
Call the executable file ExecFile with the command line parameters param.
Definition: gestfich.cpp:165
void SetExecFlags(const int aFlags)
Set (adds) specified flags for next execution of external generator of the netlist or bom.
wxBitmapButton * m_buttonDelGenerator
wxString s_bomHelpInfo
Definition: dialog_bom.cpp:52
HTML_MESSAGE_BOX * m_helpWindow
Definition: dialog_bom.cpp:69
const wxString & GetName() const
Return the customisable plugin name.
Definition: bom_plugins.h:98
void SetNetListerCommand(const wxString &aCommand)
BOM_GENERATOR_HANDLER * addGenerator(const wxString &aPath, const wxString &aName=wxEmptyString)
Definition: dialog_bom.cpp:234
static std::vector< BOM_PLUGIN_SETTINGS > DefaultBomPlugins()
void pluginInit()
Definition: dialog_bom.cpp:276
void OnShowConsoleChanged(wxCommandEvent &event) override
Definition: dialog_bom.cpp:496
static wxString GetStockPluginsPath()
Gets the stock (install) plugins path.
Definition: paths.cpp:203