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>
33#include <bom_plugins.h>
34#include <confirm.h>
35#include <dialog_bom_base.h>
37#include <eeschema_settings.h>
38#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_base.h>
42#include <netlist.h>
44#include <paths.h>
45#include <pgm_base.h>
46#include <reporter.h>
47#include <sch_edit_frame.h>
48#include <string_utils.h>
49
50#include <wx/filedlg.h>
51#include <wx/log.h>
52#include <wx/textdlg.h>
53
54wxString s_bomHelpInfo =
55#include <dialog_bom_help_md.h>
56;
57
58// BOM "plugins" are not actually plugins. They are external tools
59// (scripts or executables) called by this dialog.
60typedef std::vector< std::unique_ptr<BOM_GENERATOR_HANDLER> > BOM_GENERATOR_ARRAY;
61
62
63// The main dialog frame to run scripts to build bom
65{
66private:
70
72
73public:
74 DIALOG_BOM( SCH_EDIT_FRAME* parent );
76
77private:
78 void OnGeneratorSelected( wxCommandEvent& event ) override;
79 void OnRunGenerator( wxCommandEvent& event ) override;
80 void OnHelp( wxCommandEvent& event ) override;
81 void OnAddGenerator( wxCommandEvent& event ) override;
82 void OnRemoveGenerator( wxCommandEvent& event ) override;
83 void OnEditGenerator( wxCommandEvent& event ) override;
84 void OnCommandLineEdited( wxCommandEvent& event ) override;
85 void OnNameEdited( wxCommandEvent& event ) override;
86 void OnShowConsoleChanged( wxCommandEvent& event ) override;
87 void OnIdle( wxIdleEvent& event ) override;
88
89 void pluginInit();
91 BOM_GENERATOR_HANDLER* addGenerator( const wxString& aPath,
92 const wxString& aName = wxEmptyString );
93 bool pluginExists( const wxString& aName );
94
96 {
97 int idx = m_lbGenerators->GetSelection();
98
99 if( idx < 0 || idx >= (int)m_generators.size() )
100 return nullptr;
101
102 return m_generators[idx].get();
103 }
104
105 wxString chooseGenerator();
106};
107
108
109// Create and show DIALOG_BOM.
111{
112 DIALOG_BOM dlg( aCaller );
113
114 // QuasiModal so syntax help works
115 return dlg.ShowQuasiModal();
116}
117
118
120 DIALOG_BOM_BASE( parent ),
121 m_parent( parent ),
122 m_initialized( false ),
123 m_helpWindow( nullptr )
124{
128
130
131#ifndef __WINDOWS__
132 m_checkBoxShowConsole->Show( false );
133#endif
134
135 SetupStandardButtons( { { wxID_OK, _( "Generate" ) },
136 { wxID_CANCEL, _( "Close" ) } } );
137
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;
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, wxS( "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
234BOM_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->IsOk() )
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
258bool 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
270void 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
326void 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( NET_TYPE_BOM, fullfilename, GNL_OPT_BOM|GNL_ALL, &reporter );
347
348 m_Messages->SetValue( reportmsg );
349
350 // Force focus back on the dialog
351 SetFocus();
352}
353
354
355void 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
373void 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 = wxFileSelector( _( "Generator File" ), lastPath, wxEmptyString,
421 wxEmptyString, wxFileSelectorDefaultWildcardStr,
422 wxFD_OPEN, this );
423
424 return fullFileName;
425}
426
427
428void 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 wxString editorname = Pgm().GetTextEditor();
444
445 if( !editorname.IsEmpty() )
446 ExecuteFile( editorname, pluginFile );
447 else
448 wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
449}
450
451
452void DIALOG_BOM::OnHelp( wxCommandEvent& event )
453{
454 if( m_helpWindow )
455 {
457 return;
458 }
459
460 m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Bill of Materials Generation Help" ) );
461 m_helpWindow->SetDialogSizeInDU( 500, 350 );
462
463 wxString html_txt;
464 ConvertMarkdown2Html( wxGetTranslation( s_bomHelpInfo ), html_txt );
465
466 m_helpWindow->AddHTML_Text( html_txt );
468}
469
470
471void DIALOG_BOM::OnCommandLineEdited( wxCommandEvent& event )
472{
473 auto generator = selectedGenerator();
474
475 if( generator )
476 generator->SetCommand( m_textCtrlCommand->GetValue() );
477}
478
479
480void DIALOG_BOM::OnNameEdited( wxCommandEvent& event )
481{
482 if( m_textCtrlName->GetValue().IsEmpty() )
483 return;
484
485 int ii = m_lbGenerators->GetSelection();
486
487 if( ii < 0 )
488 return;
489
490 m_generators[ii]->SetName( m_textCtrlName->GetValue() );
491 m_lbGenerators->SetString( ii, m_generators[ii]->GetName() );
492}
493
494
495void DIALOG_BOM::OnShowConsoleChanged( wxCommandEvent& event )
496{
497#ifdef __WINDOWS__
498 static constexpr wxChar OPT_SHOW_CONSOLE[] = wxT( "show_console" );
499
500 auto plugin = selectedGenerator();
501
502 if( !plugin )
503 return;
504
505 if( m_checkBoxShowConsole->IsChecked() )
506 {
507 if( plugin->Options().Index( OPT_SHOW_CONSOLE ) == wxNOT_FOUND )
508 plugin->Options().Add( OPT_SHOW_CONSOLE );
509 }
510 else
511 {
512 plugin->Options().Remove( OPT_SHOW_CONSOLE );
513 }
514#endif
515}
516
517
518void DIALOG_BOM::OnIdle( wxIdleEvent& event )
519{
520 // On some platforms we initialize wxTextCtrls to all-selected, but we don't want that
521 // for the messages text box.
522 if( !m_initialized )
523 {
524 m_Messages->SetSelection( 0, 0 );
525 m_initialized = true;
526 }
527}
const char * name
Definition: DXF_plotter.cpp:56
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:106
const wxChar BOM_TRACE[]
Definition: bom_plugins.cpp:32
Bill of material output generator.
Definition: bom_plugins.h:46
const wxString & GetInfo() const
Return plugin description stored in the plugin header file (if available).
Definition: bom_plugins.h:68
const wxFileName & GetFile() const
Return the file name of the plugin.
Definition: bom_plugins.h:76
bool IsOk()
Return true if the plugin is ready to work, i.e.
Definition: bom_plugins.h:56
wxArrayString & Options()
Accessor to array of options.
Definition: bom_plugins.h:130
const wxString & GetName() const
Return the customisable plugin name.
Definition: bom_plugins.h:96
wxFileName FindFilePath() const
Returns the calculated path to the plugin: if the path is already absolute and exists,...
const wxString & GetCommand() const
Return the command to execute the plugin.
Definition: bom_plugins.h:114
Class DIALOG_BOM_BASE.
wxButton * m_buttonReset
STD_BITMAP_BUTTON * m_buttonEdit
wxTextCtrl * m_textCtrlName
STD_BITMAP_BUTTON * m_buttonAddGenerator
STD_BITMAP_BUTTON * m_buttonDelGenerator
wxTextCtrl * m_Messages
wxCheckBox * m_checkBoxShowConsole
wxTextCtrl * m_textCtrlCommand
wxListBox * m_lbGenerators
wxButton * m_sdbSizerOK
void OnEditGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:428
SCH_EDIT_FRAME * m_parent
Definition: dialog_bom.cpp:67
void OnRemoveGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:355
void installGeneratorsList()
Definition: dialog_bom.cpp:185
void pluginInit()
Definition: dialog_bom.cpp:276
bool m_initialized
Definition: dialog_bom.cpp:69
BOM_GENERATOR_HANDLER * selectedGenerator()
Definition: dialog_bom.cpp:95
bool pluginExists(const wxString &aName)
Definition: dialog_bom.cpp:258
BOM_GENERATOR_HANDLER * addGenerator(const wxString &aPath, const wxString &aName=wxEmptyString)
Definition: dialog_bom.cpp:234
void OnIdle(wxIdleEvent &event) override
Definition: dialog_bom.cpp:518
void OnHelp(wxCommandEvent &event) override
Definition: dialog_bom.cpp:452
void OnCommandLineEdited(wxCommandEvent &event) override
Definition: dialog_bom.cpp:471
wxString chooseGenerator()
Definition: dialog_bom.cpp:413
void OnGeneratorSelected(wxCommandEvent &event) override
Definition: dialog_bom.cpp:270
void OnNameEdited(wxCommandEvent &event) override
Definition: dialog_bom.cpp:480
void OnRunGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:326
HTML_MESSAGE_BOX * m_helpWindow
Definition: dialog_bom.cpp:71
void OnShowConsoleChanged(wxCommandEvent &event) override
Definition: dialog_bom.cpp:495
BOM_GENERATOR_ARRAY m_generators
Definition: dialog_bom.cpp:68
DIALOG_BOM(SCH_EDIT_FRAME *parent)
Definition: dialog_bom.cpp:119
void OnAddGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:373
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 SetupStandardButtons(std::map< int, wxString > aLabels={})
int ShowQuasiModal()
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
virtual void ClearMsgPanel()
Clear all messages from the message panel.
static std::vector< BOM_PLUGIN_SETTINGS > DefaultBomPlugins()
void SetDialogSizeInDU(int aWidth, int aHeight)
Set the dialog size, using a "logical" value.
void AddHTML_Text(const wxString &message)
Add HTML text (without any change) to message list.
void ShowModeless()
Show a modeless version of the dialog (without an OK button).
static wxString GetUserPluginsPath()
Gets the user path for plugins.
Definition: paths.cpp:53
static wxString GetStockPluginsPath()
Gets the stock (install) plugins path.
Definition: paths.cpp:262
wxString GetFileName() const override
Helper to retrieve the filename from the root sheet screen.
Definition: schematic.cpp:199
EESCHEMA_SETTINGS * eeconfig() const
Schematic editor (Eeschema) main window.
void SetExecFlags(const int aFlags)
Set (adds) specified flags for next execution of external generator of the netlist or bom.
bool ReadyToNetlist(const wxString &aAnnotateMessage)
Check if we are ready to write a netlist file for the current schematic.
bool WriteNetListFile(int aFormat, const wxString &aFullFileName, unsigned aNetlistOptions, REPORTER *aReporter=nullptr)
Create a netlist file.
SCHEMATIC & Schematic() const
void SetNetListerCommand(const wxString &aCommand)
void SetBitmap(const wxBitmap &aBmp)
A wrapper for reporting to a wxString object.
Definition: reporter.h:164
void DisplayError(wxWindow *aParent, const wxString &aText, int aDisplayTime)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:300
This file is part of the common library.
int InvokeDialogCreateBOM(SCH_EDIT_FRAME *aCaller)
Create and show DIALOG_BOM and return whatever DIALOG_BOM::ShowModal() returns.
Definition: dialog_bom.cpp:110
wxString s_bomHelpInfo
Definition: dialog_bom.cpp:54
std::vector< std::unique_ptr< BOM_GENERATOR_HANDLER > > BOM_GENERATOR_ARRAY
Definition: dialog_bom.cpp:60
#define _(s)
int ExecuteFile(const wxString &aEditorName, const wxString &aFileName, wxProcess *aCallback)
Call the executable file aEditorName with the parameter aFileName.
Definition: gestfich.cpp:115
Some functions to handle hotkeys in KiCad.
@ NET_TYPE_BOM
Definition: netlist.h:37
#define GNL_ALL
@ GNL_OPT_BOM
see class PGM_BASE
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
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:111
void ConvertMarkdown2Html(const wxString &aMarkdownInput, wxString &aHtmlOutput)
std::vector< BOM_PLUGIN_SETTINGS > plugins