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-2020 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 <bom_plugins.h>
32 #include <confirm.h>
33 #include <dialog_bom_base.h>
34 #include <dialog_helpers.h>
35 #include <eeschema_settings.h>
36 #include <gestfich.h>
38 #include <i18n_utility.h> // for _HKI definition used in dialog_bom_help_md.h
39 #include <invoke_sch_dialog.h>
40 #include <kiface_i.h>
41 #include <netlist.h>
42 #include <netlist_exporter_xml.h>
43 #include <pgm_base.h>
44 #include <reporter.h>
45 #include <sch_edit_frame.h>
46 #include <schematic.h>
47 #include <paths.h>
48 
49 #include <wx/filedlg.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, const wxString& aName = wxEmptyString );
90  bool pluginExists( const wxString& aName );
91 
93  {
94  int idx = m_lbGenerators->GetSelection();
95 
96  if( idx < 0 || idx >= (int)m_generators.size() )
97  return nullptr;
98 
99  return m_generators[idx].get();
100  }
101 
102  wxString chooseGenerator();
103 };
104 
105 
106 // Create and show DIALOG_BOM.
108 {
109  DIALOG_BOM dlg( aCaller );
110 
111  // QuasiModal so syntax help works
112  return dlg.ShowQuasiModal();
113 }
114 
115 
117  DIALOG_BOM_BASE( parent ),
118  m_parent( parent ),
119  m_initialized( false ),
120  m_helpWindow( nullptr )
121 {
124  m_buttonEdit->SetBitmap( KiBitmap( small_edit_xpm ) );
125 
127 
128 #ifndef __WINDOWS__
129  m_checkBoxShowConsole->Show( false );
130 #endif
131 
132  m_sdbSizerOK->SetLabel( _( "Generate" ) );
133  m_sdbSizerCancel->SetLabel( _( "Close" ) );
134  m_sdbSizer->Layout();
135 
137  m_sdbSizerOK->SetDefault();
138 
139  // Now all widgets have the size fixed, call FinishDialogSettings
141 
142  m_buttonReset->Bind( wxEVT_BUTTON,
143  [&]( wxCommandEvent& )
144  {
146 
147  cfg->m_BomPanel.selected_plugin = wxEmptyString;
148  cfg->m_BomPanel.plugins = cfg->DefaultBomPlugins();
149 
151  } );
152 }
153 
154 
156 {
157  if( m_helpWindow )
158  m_helpWindow->Destroy();
159 
161 
162  cfg->m_BomPanel.plugins.clear();
163 
164  for( const std::unique_ptr<BOM_GENERATOR_HANDLER>& plugin : m_generators )
165  {
166  wxString name = plugin->GetName();
167  wxFileName path( plugin->GetStoredPath() );
168 
169  // handle empty nickname by stripping path
170  if( name.IsEmpty() )
171  name = path.GetName();
172 
173  EESCHEMA_SETTINGS::BOM_PLUGIN_SETTINGS setting( name, path.GetFullPath() );
174  setting.command = plugin->GetCommand();
175 
176  cfg->m_BomPanel.plugins.emplace_back( setting );
177  }
178 
179  cfg->m_BomPanel.selected_plugin = m_lbGenerators->GetStringSelection().ToStdString();
180 }
181 
182 
183 // Read the initialized plugins in config and fill the list of names
185 {
187 
188  wxString active_plugin_name = cfg->m_BomPanel.selected_plugin;
189 
190  m_generators.clear();
191 
193  {
194  auto plugin = std::make_unique<BOM_GENERATOR_HANDLER>( setting.path );
195 
196  plugin->SetName( setting.name );
197 
198  if( !setting.command.IsEmpty() )
199  plugin->SetCommand( setting.command );
200 
201  m_generators.emplace_back( std::move( plugin ) );
202  }
203 
204  m_lbGenerators->Clear();
205 
206  if( !m_generators.empty() )
207  {
208  for( unsigned ii = 0; ii < m_generators.size(); ii++ )
209  {
210  wxString name = m_generators[ii]->GetName();
211 
212  if( !m_generators[ii]->FindFilePath().Exists( wxFILE_EXISTS_REGULAR ) )
213  {
214  wxLogTrace( BOM_TRACE, "BOM plugin %s not found",
215  m_generators[ii]->FindFilePath().GetFullName() );
216  name.Append( wxT( " " ) + _( "(file missing)" ) );
217 
218  if( active_plugin_name == name )
219  active_plugin_name.Clear();
220  }
221 
222  m_lbGenerators->Append( name );
223 
224  if( active_plugin_name == name )
225  m_lbGenerators->SetSelection( ii );
226  }
227  }
228 
229  pluginInit();
230 }
231 
232 
233 BOM_GENERATOR_HANDLER* DIALOG_BOM::addGenerator( const wxString& aPath, const wxString& aName )
234 {
235  BOM_GENERATOR_HANDLER* ret = nullptr;
236  auto plugin = std::make_unique<BOM_GENERATOR_HANDLER>( aPath );
237 
238  if( !plugin )
239  return nullptr;
240 
241  if( !aName.IsEmpty() )
242  {
243  plugin->SetName( aName );
244  m_lbGenerators->Append( aName );
245  }
246  else
247  {
248  m_lbGenerators->Append( plugin->GetName() );
249  }
250 
251  ret = plugin.get();
252  m_generators.push_back( std::move( plugin ) );
253  return ret;
254 }
255 
256 
257 bool DIALOG_BOM::pluginExists( const wxString& aName )
258 {
259  for( unsigned ii = 0; ii < m_generators.size(); ii++ )
260  {
261  if( aName == m_generators[ii]->GetName() )
262  return true;
263  }
264 
265  return false;
266 }
267 
268 
269 void DIALOG_BOM::OnGeneratorSelected( wxCommandEvent& event )
270 {
271  pluginInit();
272 }
273 
274 
276 {
278 
279  if( !plugin )
280  {
281  m_textCtrlName->SetValue( wxEmptyString );
282  m_textCtrlCommand->SetValue( wxEmptyString );
283  m_Messages->SetValue( wxEmptyString );
284  return;
285  }
286 
287  if( !plugin->FindFilePath().Exists( wxFILE_EXISTS_REGULAR ) )
288  {
289  m_textCtrlName->SetValue( wxEmptyString );
290  m_textCtrlCommand->SetValue( wxEmptyString );
291 
292  wxString msg =
293  wxString::Format( _( "The selected BOM generator script %s could not be found." ),
294  plugin->GetFile().GetFullPath() );
295 
296  if( !plugin->GetFile().IsAbsolute() )
297  {
298  msg.Append( wxString::Format( _( "\n\nSearched:\n\t%s\n\t%s" ),
301  }
302 
303  m_Messages->SetValue( msg );
304  return;
305  }
306 
307  m_textCtrlName->SetValue( plugin->GetName() );
308  m_textCtrlCommand->SetValue( plugin->GetCommand() );
309  m_Messages->SetValue( plugin->GetInfo() );
310  m_Messages->SetSelection( 0, 0 );
311 
312 #ifdef __WINDOWS__
313  if( plugin->Options().Index( wxT( "show_console" ) ) == wxNOT_FOUND )
314  m_checkBoxShowConsole->SetValue( false );
315  else
316  m_checkBoxShowConsole->SetValue( true );
317 #endif
318 
319  // A plugin can be not working, so do not left the OK button enabled if
320  // the plugin is not ready to use
321  m_sdbSizerOK->Enable( plugin->IsOk() );
322 }
323 
324 
325 void DIALOG_BOM::OnRunGenerator( wxCommandEvent& event )
326 {
327  // Calculate the xml netlist filename
328  wxFileName fn = m_parent->Schematic().GetFileName();
329 
330  fn.SetPath( wxPathOnly( Prj().GetProjectFullName() ) );
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)
Checks 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:253
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
bool WriteNetListFile(int aFormat, const wxString &aFullFileName, unsigned aNetlistOptions, REPORTER *aReporter=NULL)
Create a netlist file.
This file is part of the common library TODO brief description.
DIALOG_BOM(SCH_EDIT_FRAME *parent)
Definition: dialog_bom.cpp:116
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:257
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:107
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:98
void OnIdle(wxIdleEvent &event) override
Definition: dialog_bom.cpp:519
void OnGeneratorSelected(wxCommandEvent &event) override
Definition: dialog_bom.cpp:269
void OnHelp(wxCommandEvent &event) override
Definition: dialog_bom.cpp:453
const wxChar BOM_TRACE[]
Definition: bom_plugins.cpp:31
wxButton * m_sdbSizerOK
EESCHEMA_SETTINGS * eeconfig() const
wxBitmap KiBitmap(BITMAP_DEF aBitmap)
Construct a wxBitmap from a memory record, held in a BITMAP_DEF.
Definition: bitmap.cpp:82
wxButton * m_buttonReset
static wxString GetUserPluginsPath()
Gets the user path for plugins.
Definition: paths.cpp:52
wxTextCtrl * m_textCtrlName
void OnRunGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:325
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,...
int ShowQuasiModal()
PROJECT & Prj() const
Return a reference to the PROJECT associated with this KIWAY.
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.
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:119
void installGeneratorsList()
Definition: dialog_bom.cpp:184
HTML_MESSAGE_BOX.
virtual void ClearMsgPanel()
Clear all messages from the message panel.
wxTextCtrl * m_Messages
bool m_initialized
Definition: dialog_bom.cpp:67
const BITMAP_OPAQUE small_trash_xpm[1]
Definition: small_trash.cpp:23
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...
const BITMAP_OPAQUE small_edit_xpm[1]
Definition: small_edit.cpp:22
void OnAddGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:373
see class PGM_BASE
const char * name
Definition: DXF_plotter.cpp:59
#define _(s)
Definition: 3d_actions.cpp:33
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:92
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)
const BITMAP_OPAQUE small_plus_xpm[1]
Definition: small_plus.cpp:18
BOM_GENERATOR_HANDLER * addGenerator(const wxString &aPath, const wxString &aName=wxEmptyString)
Definition: dialog_bom.cpp:233
static std::vector< BOM_PLUGIN_SETTINGS > DefaultBomPlugins()
void pluginInit()
Definition: dialog_bom.cpp:275
void OnShowConsoleChanged(wxCommandEvent &event) override
Definition: dialog_bom.cpp:496
static wxString GetStockPluginsPath()
Gets the stock (install) plugins path.
Definition: paths.cpp:165