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>
36 #include <eeschema_settings.h>
37 #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_base.h>
41 #include <netlist.h>
42 #include <netlist_exporter_xml.h>
43 #include <paths.h>
44 #include <pgm_base.h>
45 #include <reporter.h>
46 #include <sch_edit_frame.h>
47 #include <string_utils.h>
48 
49 #include <wx/filedlg.h>
50 #include <wx/log.h>
51 #include <wx/textdlg.h>
52 
53 wxString s_bomHelpInfo =
54 #include <dialog_bom_help_md.h>
55 ;
56 
57 // BOM "plugins" are not actually plugins. They are external tools
58 // (scripts or executables) called by this dialog.
59 typedef std::vector< std::unique_ptr<BOM_GENERATOR_HANDLER> > BOM_GENERATOR_ARRAY;
60 
61 
62 // The main dialog frame to run scripts to build bom
64 {
65 private:
69 
71 
72 public:
73  DIALOG_BOM( SCH_EDIT_FRAME* parent );
74  ~DIALOG_BOM();
75 
76 private:
77  void OnGeneratorSelected( wxCommandEvent& event ) override;
78  void OnRunGenerator( wxCommandEvent& event ) override;
79  void OnHelp( wxCommandEvent& event ) override;
80  void OnAddGenerator( wxCommandEvent& event ) override;
81  void OnRemoveGenerator( wxCommandEvent& event ) override;
82  void OnEditGenerator( wxCommandEvent& event ) override;
83  void OnCommandLineEdited( wxCommandEvent& event ) override;
84  void OnNameEdited( wxCommandEvent& event ) override;
85  void OnShowConsoleChanged( wxCommandEvent& event ) override;
86  void OnIdle( wxIdleEvent& event ) override;
87 
88  void pluginInit();
89  void installGeneratorsList();
90  BOM_GENERATOR_HANDLER* addGenerator( const wxString& aPath,
91  const wxString& aName = wxEmptyString );
92  bool pluginExists( const wxString& aName );
93 
95  {
96  int idx = m_lbGenerators->GetSelection();
97 
98  if( idx < 0 || idx >= (int)m_generators.size() )
99  return nullptr;
100 
101  return m_generators[idx].get();
102  }
103 
104  wxString chooseGenerator();
105 };
106 
107 
108 // Create and show DIALOG_BOM.
110 {
111  DIALOG_BOM dlg( aCaller );
112 
113  // QuasiModal so syntax help works
114  return dlg.ShowQuasiModal();
115 }
116 
117 
119  DIALOG_BOM_BASE( parent ),
120  m_parent( parent ),
121  m_initialized( false ),
122  m_helpWindow( nullptr )
123 {
126  m_buttonEdit->SetBitmap( KiBitmap( BITMAPS::small_edit ) );
127 
129 
130 #ifndef __WINDOWS__
131  m_checkBoxShowConsole->Show( false );
132 #endif
133 
134  m_sdbSizerOK->SetLabel( _( "Generate" ) );
135  m_sdbSizerCancel->SetLabel( _( "Close" ) );
136  m_sdbSizer->Layout();
137 
139  m_sdbSizerOK->SetDefault();
140 
141  // Now all widgets have the size fixed, call FinishDialogSettings
143 
144  m_buttonReset->Bind( wxEVT_BUTTON,
145  [&]( wxCommandEvent& )
146  {
148 
149  cfg->m_BomPanel.selected_plugin = wxEmptyString;
150  cfg->m_BomPanel.plugins = cfg->DefaultBomPlugins();
151 
153  } );
154 }
155 
156 
158 {
159  if( m_helpWindow )
160  m_helpWindow->Destroy();
161 
163 
164  cfg->m_BomPanel.plugins.clear();
165 
166  for( const std::unique_ptr<BOM_GENERATOR_HANDLER>& plugin : m_generators )
167  {
168  wxString name = plugin->GetName();
169  wxFileName path( plugin->GetStoredPath() );
170 
171  // handle empty nickname by stripping path
172  if( name.IsEmpty() )
173  name = path.GetName();
174 
175  EESCHEMA_SETTINGS::BOM_PLUGIN_SETTINGS setting( name, path.GetFullPath() );
176  setting.command = plugin->GetCommand();
177 
178  cfg->m_BomPanel.plugins.emplace_back( setting );
179  }
180 
181  cfg->m_BomPanel.selected_plugin = m_lbGenerators->GetStringSelection().ToStdString();
182 }
183 
184 
185 // Read the initialized plugins in config and fill the list of names
187 {
189 
190  wxString active_plugin_name = cfg->m_BomPanel.selected_plugin;
191 
192  m_generators.clear();
193 
195  {
196  auto plugin = std::make_unique<BOM_GENERATOR_HANDLER>( setting.path );
197 
198  plugin->SetName( setting.name );
199 
200  if( !setting.command.IsEmpty() )
201  plugin->SetCommand( setting.command );
202 
203  m_generators.emplace_back( std::move( plugin ) );
204  }
205 
206  m_lbGenerators->Clear();
207 
208  if( !m_generators.empty() )
209  {
210  for( unsigned ii = 0; ii < m_generators.size(); ii++ )
211  {
212  wxString name = m_generators[ii]->GetName();
213 
214  if( !m_generators[ii]->FindFilePath().Exists( wxFILE_EXISTS_REGULAR ) )
215  {
216  wxLogTrace( BOM_TRACE, wxT( "BOM plugin %s not found" ),
217  m_generators[ii]->FindFilePath().GetFullName() );
218  name.Append( wxT( " " ) + _( "(file missing)" ) );
219 
220  if( active_plugin_name == name )
221  active_plugin_name.Clear();
222  }
223 
224  m_lbGenerators->Append( name );
225 
226  if( active_plugin_name == name )
227  m_lbGenerators->SetSelection( ii );
228  }
229  }
230 
231  pluginInit();
232 }
233 
234 
235 BOM_GENERATOR_HANDLER* DIALOG_BOM::addGenerator( const wxString& aPath, const wxString& aName )
236 {
237  BOM_GENERATOR_HANDLER* ret = nullptr;
238  auto plugin = std::make_unique<BOM_GENERATOR_HANDLER>( aPath );
239 
240  if( !plugin )
241  return nullptr;
242 
243  if( !aName.IsEmpty() )
244  {
245  plugin->SetName( aName );
246  m_lbGenerators->Append( aName );
247  }
248  else
249  {
250  m_lbGenerators->Append( plugin->GetName() );
251  }
252 
253  ret = plugin.get();
254  m_generators.push_back( std::move( plugin ) );
255  return ret;
256 }
257 
258 
259 bool DIALOG_BOM::pluginExists( const wxString& aName )
260 {
261  for( unsigned ii = 0; ii < m_generators.size(); ii++ )
262  {
263  if( aName == m_generators[ii]->GetName() )
264  return true;
265  }
266 
267  return false;
268 }
269 
270 
271 void DIALOG_BOM::OnGeneratorSelected( wxCommandEvent& event )
272 {
273  pluginInit();
274 }
275 
276 
278 {
280 
281  if( !plugin )
282  {
283  m_textCtrlName->SetValue( wxEmptyString );
284  m_textCtrlCommand->SetValue( wxEmptyString );
285  m_Messages->SetValue( wxEmptyString );
286  return;
287  }
288 
289  if( !plugin->FindFilePath().Exists( wxFILE_EXISTS_REGULAR ) )
290  {
291  m_textCtrlName->SetValue( wxEmptyString );
292  m_textCtrlCommand->SetValue( wxEmptyString );
293 
294  wxString msg =
295  wxString::Format( _( "The selected BOM generator script %s could not be found." ),
296  plugin->GetFile().GetFullPath() );
297 
298  if( !plugin->GetFile().IsAbsolute() )
299  {
300  msg.Append( wxString::Format( _( "\n\nSearched:\n\t%s\n\t%s" ),
303  }
304 
305  m_Messages->SetValue( msg );
306  return;
307  }
308 
309  m_textCtrlName->SetValue( plugin->GetName() );
310  m_textCtrlCommand->SetValue( plugin->GetCommand() );
311  m_Messages->SetValue( plugin->GetInfo() );
312  m_Messages->SetSelection( 0, 0 );
313 
314 #ifdef __WINDOWS__
315  if( plugin->Options().Index( wxT( "show_console" ) ) == wxNOT_FOUND )
316  m_checkBoxShowConsole->SetValue( false );
317  else
318  m_checkBoxShowConsole->SetValue( true );
319 #endif
320 
321  // A plugin can be not working, so do not left the OK button enabled if
322  // the plugin is not ready to use
323  m_sdbSizerOK->Enable( plugin->IsOk() );
324 }
325 
326 
327 void DIALOG_BOM::OnRunGenerator( wxCommandEvent& event )
328 {
329  // Calculate the xml netlist filename
330  wxFileName fn = m_parent->Schematic().GetFileName();
331 
332  fn.ClearExt();
333 
334  wxString fullfilename = fn.GetFullPath();
336 
337  wxString reportmsg;
338  WX_STRING_REPORTER reporter( &reportmsg );
340 
341 #ifdef __WINDOWS__
342  if( m_checkBoxShowConsole->IsChecked() )
343  m_parent->SetExecFlags( wxEXEC_SHOW_CONSOLE );
344 #endif
345 
346  if( m_parent->ReadyToNetlist( _( "Generating BOM requires a fully annotated schematic." ) ) )
347  m_parent->WriteNetListFile( NET_TYPE_BOM, fullfilename, GNL_OPT_BOM, &reporter );
348 
349  m_Messages->SetValue( reportmsg );
350 
351  // Force focus back on the dialog
352  SetFocus();
353 }
354 
355 
356 void DIALOG_BOM::OnRemoveGenerator( wxCommandEvent& event )
357 {
358  int ii = m_lbGenerators->GetSelection();
359 
360  if( ii < 0 )
361  return;
362 
363  m_lbGenerators->Delete( ii );
364  m_generators.erase( m_generators.begin() + ii );
365 
366  // Select the next item, if exists
367  if( m_lbGenerators->GetCount() )
368  m_lbGenerators->SetSelection( std::min( ii, (int) m_lbGenerators->GetCount() - 1 ) );
369 
370  pluginInit();
371 }
372 
373 
374 void DIALOG_BOM::OnAddGenerator( wxCommandEvent& event )
375 {
376  wxString filename = chooseGenerator();
377 
378  if( filename.IsEmpty() )
379  return;
380 
381  // Creates a new plugin entry
382  wxFileName fn( filename );
383  wxString name = wxGetTextFromUser( _( "Generator nickname:" ), _( "Add Generator" ),
384  fn.GetName(), this );
385 
386  if( name.IsEmpty() )
387  return;
388 
389  // Verify if it does not exists
390  if( pluginExists( name ) )
391  {
392  wxMessageBox( wxString::Format( _( "Nickname '%s' already in use." ), name ) );
393  return;
394  }
395 
396  try
397  {
398  auto plugin = addGenerator( fn.GetFullPath(), name );
399 
400  if( plugin )
401  {
402  m_lbGenerators->SetSelection( m_lbGenerators->GetCount() - 1 );
403  m_textCtrlCommand->SetValue( plugin->GetCommand() );
404  pluginInit();
405  }
406  }
407  catch( const std::runtime_error& e )
408  {
409  DisplayError( this, e.what() );
410  }
411 }
412 
413 
415 {
416  static wxString lastPath;
417 
418  if( lastPath.IsEmpty() )
419  lastPath = PATHS::GetUserPluginsPath();
420 
421  wxString fullFileName = wxFileSelector( _( "Generator File" ), lastPath, wxEmptyString,
422  wxEmptyString, wxFileSelectorDefaultWildcardStr,
423  wxFD_OPEN, this );
424 
425  return fullFileName;
426 }
427 
428 
429 void DIALOG_BOM::OnEditGenerator( wxCommandEvent& event )
430 {
431  auto plugin = selectedGenerator();
432 
433  if( !plugin )
434  return;
435 
436  wxString pluginFile = plugin->GetFile().GetFullPath();
437 
438  if( pluginFile.Length() <= 2 ) // if name != ""
439  {
440  wxMessageBox( _( "Generator file name not found." ) );
441  return;
442  }
443 
444  wxString editorname = Pgm().GetTextEditor();
445 
446  if( !editorname.IsEmpty() )
447  ExecuteFile( 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->AddHTML_Text( 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:279
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:429
wxStdDialogButtonSizer * m_sdbSizer
wxArrayString & Options()
Accessor to array of options.
Definition: bom_plugins.h:130
DIALOG_BOM(SCH_EDIT_FRAME *parent)
Definition: dialog_bom.cpp:118
This file is part of the common library.
void OnRemoveGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:356
const wxFileName & GetFile() const
Return the file name of the plugin.
Definition: bom_plugins.h:76
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:68
bool pluginExists(const wxString &aName)
Definition: dialog_bom.cpp:259
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:109
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:106
int ExecuteFile(const wxString &aEditorName, const wxString &aFileName, wxProcess *aCallback)
Call the executable file aEditorName with the parameter aFileName.
Definition: gestfich.cpp:115
bool IsOk()
Return true if the plugin is ready to work, i.e.
Definition: bom_plugins.h:56
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:271
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:327
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()
void ConvertMarkdown2Html(const wxString &aMarkdownInput, wxString &aHtmlOutput)
wxTextCtrl * m_textCtrlCommand
SCHEMATIC & Schematic() const
#define _(s)
wxString GetFileName() const override
Helper to retrieve the filename from the root sheet screen.
Definition: schematic.cpp:161
void installGeneratorsList()
Definition: dialog_bom.cpp:186
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:68
wxString chooseGenerator()
Definition: dialog_bom.cpp:414
BOM_GENERATOR_ARRAY m_generators
Definition: dialog_bom.cpp:67
A wrapper for reporting to a wxString object.
Definition: reporter.h:163
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...
std::vector< std::unique_ptr< BOM_GENERATOR_HANDLER > > BOM_GENERATOR_ARRAY
Definition: dialog_bom.cpp:59
void OnAddGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:374
see class PGM_BASE
const char * name
Definition: DXF_plotter.cpp:56
Class DIALOG_BOM_BASE.
void AddHTML_Text(const wxString &message)
Add HTML text (without any change) to message list.
const wxString & GetCommand() const
Return the command to execute the plugin.
Definition: bom_plugins.h:114
wxListBox * m_lbGenerators
wxButton * m_sdbSizerCancel
SCH_EDIT_FRAME * m_parent
Definition: dialog_bom.cpp:66
BOM_GENERATOR_HANDLER * selectedGenerator()
Definition: dialog_bom.cpp:94
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:53
HTML_MESSAGE_BOX * m_helpWindow
Definition: dialog_bom.cpp:70
const wxString & GetName() const
Return the customisable plugin name.
Definition: bom_plugins.h:96
void SetNetListerCommand(const wxString &aCommand)
BOM_GENERATOR_HANDLER * addGenerator(const wxString &aPath, const wxString &aName=wxEmptyString)
Definition: dialog_bom.cpp:235
static std::vector< BOM_PLUGIN_SETTINGS > DefaultBomPlugins()
void pluginInit()
Definition: dialog_bom.cpp:277
void OnShowConsoleChanged(wxCommandEvent &event) override
Definition: dialog_bom.cpp:496
static wxString GetStockPluginsPath()
Gets the stock (install) plugins path.
Definition: paths.cpp:262