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>
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
53wxString 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.
59typedef 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{
65private:
69
71
72public:
73 DIALOG_BOM( SCH_EDIT_FRAME* parent );
75
76private:
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();
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{
127
129
130#ifndef __WINDOWS__
131 m_checkBoxShowConsole->Show( false );
132#endif
133
134 SetupStandardButtons( { { wxID_OK, _( "Generate" ) },
135 { wxID_CANCEL, _( "Close" ) } } );
136
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;
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
233BOM_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->IsOk() )
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
257bool 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
269void 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
325void DIALOG_BOM::OnRunGenerator( wxCommandEvent& event )
326{
327 // Calculate the xml netlist filename
328 wxFileName fn = m_parent->Schematic().GetFileName();
329
330 fn.ClearExt();
331
332 wxString fullfilename = fn.GetFullPath();
334
335 wxString reportmsg;
336 WX_STRING_REPORTER reporter( &reportmsg );
338
339#ifdef __WINDOWS__
340 if( m_checkBoxShowConsole->IsChecked() )
341 m_parent->SetExecFlags( wxEXEC_SHOW_CONSOLE );
342#endif
343
344 if( m_parent->ReadyToNetlist( _( "Generating BOM requires a fully annotated schematic." ) ) )
345 m_parent->WriteNetListFile( NET_TYPE_BOM, fullfilename, GNL_OPT_BOM|GNL_ALL, &reporter );
346
347 m_Messages->SetValue( reportmsg );
348
349 // Force focus back on the dialog
350 SetFocus();
351}
352
353
354void DIALOG_BOM::OnRemoveGenerator( wxCommandEvent& event )
355{
356 int ii = m_lbGenerators->GetSelection();
357
358 if( ii < 0 )
359 return;
360
361 m_lbGenerators->Delete( ii );
362 m_generators.erase( m_generators.begin() + ii );
363
364 // Select the next item, if exists
365 if( m_lbGenerators->GetCount() )
366 m_lbGenerators->SetSelection( std::min( ii, (int) m_lbGenerators->GetCount() - 1 ) );
367
368 pluginInit();
369}
370
371
372void DIALOG_BOM::OnAddGenerator( wxCommandEvent& event )
373{
374 wxString filename = chooseGenerator();
375
376 if( filename.IsEmpty() )
377 return;
378
379 // Creates a new plugin entry
380 wxFileName fn( filename );
381 wxString name = wxGetTextFromUser( _( "Generator nickname:" ), _( "Add Generator" ),
382 fn.GetName(), this );
383
384 if( name.IsEmpty() )
385 return;
386
387 // Verify if it does not exists
388 if( pluginExists( name ) )
389 {
390 wxMessageBox( wxString::Format( _( "Nickname '%s' already in use." ), name ) );
391 return;
392 }
393
394 try
395 {
396 auto plugin = addGenerator( fn.GetFullPath(), name );
397
398 if( plugin )
399 {
400 m_lbGenerators->SetSelection( m_lbGenerators->GetCount() - 1 );
401 m_textCtrlCommand->SetValue( plugin->GetCommand() );
402 pluginInit();
403 }
404 }
405 catch( const std::runtime_error& e )
406 {
407 DisplayError( this, e.what() );
408 }
409}
410
411
413{
414 static wxString lastPath;
415
416 if( lastPath.IsEmpty() )
417 lastPath = PATHS::GetUserPluginsPath();
418
419 wxString fullFileName = wxFileSelector( _( "Generator File" ), lastPath, wxEmptyString,
420 wxEmptyString, wxFileSelectorDefaultWildcardStr,
421 wxFD_OPEN, this );
422
423 return fullFileName;
424}
425
426
427void DIALOG_BOM::OnEditGenerator( wxCommandEvent& event )
428{
429 auto plugin = selectedGenerator();
430
431 if( !plugin )
432 return;
433
434 wxString pluginFile = plugin->GetFile().GetFullPath();
435
436 if( pluginFile.Length() <= 2 ) // if name != ""
437 {
438 wxMessageBox( _( "Generator file name not found." ) );
439 return;
440 }
441
442 wxString editorname = Pgm().GetTextEditor();
443
444 if( !editorname.IsEmpty() )
445 ExecuteFile( editorname, pluginFile );
446 else
447 wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
448}
449
450
451void DIALOG_BOM::OnHelp( wxCommandEvent& event )
452{
453 if( m_helpWindow )
454 {
456 return;
457 }
458
459 m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Bill of Materials Generation Help" ) );
460 m_helpWindow->SetDialogSizeInDU( 500, 350 );
461
462 wxString html_txt;
463 ConvertMarkdown2Html( wxGetTranslation( s_bomHelpInfo ), html_txt );
464
465 m_helpWindow->AddHTML_Text( html_txt );
467}
468
469
470void DIALOG_BOM::OnCommandLineEdited( wxCommandEvent& event )
471{
472 auto generator = selectedGenerator();
473
474 if( generator )
475 generator->SetCommand( m_textCtrlCommand->GetValue() );
476}
477
478
479void DIALOG_BOM::OnNameEdited( wxCommandEvent& event )
480{
481 if( m_textCtrlName->GetValue().IsEmpty() )
482 return;
483
484 int ii = m_lbGenerators->GetSelection();
485
486 if( ii < 0 )
487 return;
488
489 m_generators[ii]->SetName( m_textCtrlName->GetValue() );
490 m_lbGenerators->SetString( ii, m_generators[ii]->GetName() );
491}
492
493
494void DIALOG_BOM::OnShowConsoleChanged( wxCommandEvent& event )
495{
496#ifdef __WINDOWS__
497 static constexpr wxChar OPT_SHOW_CONSOLE[] = wxT( "show_console" );
498
499 auto plugin = selectedGenerator();
500
501 if( !plugin )
502 return;
503
504 if( m_checkBoxShowConsole->IsChecked() )
505 {
506 if( plugin->Options().Index( OPT_SHOW_CONSOLE ) == wxNOT_FOUND )
507 plugin->Options().Add( OPT_SHOW_CONSOLE );
508 }
509 else
510 {
511 plugin->Options().Remove( OPT_SHOW_CONSOLE );
512 }
513#endif
514}
515
516
517void DIALOG_BOM::OnIdle( wxIdleEvent& event )
518{
519 // On some platforms we initialize wxTextCtrls to all-selected, but we don't want that
520 // for the messages text box.
521 if( !m_initialized )
522 {
523 m_Messages->SetSelection( 0, 0 );
524 m_initialized = true;
525 }
526}
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:105
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
wxBitmapButton * m_buttonEdit
wxTextCtrl * m_textCtrlName
wxBitmapButton * m_buttonAddGenerator
wxBitmapButton * 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:427
SCH_EDIT_FRAME * m_parent
Definition: dialog_bom.cpp:66
void OnRemoveGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:354
void installGeneratorsList()
Definition: dialog_bom.cpp:184
void pluginInit()
Definition: dialog_bom.cpp:275
bool m_initialized
Definition: dialog_bom.cpp:68
BOM_GENERATOR_HANDLER * selectedGenerator()
Definition: dialog_bom.cpp:94
bool pluginExists(const wxString &aName)
Definition: dialog_bom.cpp:257
BOM_GENERATOR_HANDLER * addGenerator(const wxString &aPath, const wxString &aName=wxEmptyString)
Definition: dialog_bom.cpp:233
void OnIdle(wxIdleEvent &event) override
Definition: dialog_bom.cpp:517
void OnHelp(wxCommandEvent &event) override
Definition: dialog_bom.cpp:451
void OnCommandLineEdited(wxCommandEvent &event) override
Definition: dialog_bom.cpp:470
wxString chooseGenerator()
Definition: dialog_bom.cpp:412
void OnGeneratorSelected(wxCommandEvent &event) override
Definition: dialog_bom.cpp:269
void OnNameEdited(wxCommandEvent &event) override
Definition: dialog_bom.cpp:479
void OnRunGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:325
HTML_MESSAGE_BOX * m_helpWindow
Definition: dialog_bom.cpp:70
void OnShowConsoleChanged(wxCommandEvent &event) override
Definition: dialog_bom.cpp:494
BOM_GENERATOR_ARRAY m_generators
Definition: dialog_bom.cpp:67
DIALOG_BOM(SCH_EDIT_FRAME *parent)
Definition: dialog_bom.cpp:118
void OnAddGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:372
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:166
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)
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:280
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:109
wxString s_bomHelpInfo
Definition: dialog_bom.cpp:53
std::vector< std::unique_ptr< BOM_GENERATOR_HANDLER > > BOM_GENERATOR_ARRAY
Definition: dialog_bom.cpp:59
#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