KiCad PCB EDA Suite
Loading...
Searching...
No Matches
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/msgdlg.h>
53#include <wx/textdlg.h>
54
55wxString s_bomHelpInfo =
56#include <dialog_bom_help_md.h>
57;
58
59// BOM "plugins" are not actually plugins. They are external tools
60// (scripts or executables) called by this dialog.
61typedef std::vector< std::unique_ptr<BOM_GENERATOR_HANDLER> > BOM_GENERATOR_ARRAY;
62
63
64// The main dialog frame to run scripts to build bom
66{
67private:
71
73
74public:
75 DIALOG_BOM( SCH_EDIT_FRAME* parent );
77
78private:
79 void OnGeneratorSelected( wxCommandEvent& event ) override;
80 void OnRunGenerator( wxCommandEvent& event ) override;
81 void OnHelp( wxCommandEvent& event ) override;
82 void OnAddGenerator( wxCommandEvent& event ) override;
83 void OnRemoveGenerator( wxCommandEvent& event ) override;
84 void OnEditGenerator( wxCommandEvent& event ) override;
85 void OnCommandLineEdited( wxCommandEvent& event ) override;
86 void OnNameEdited( wxCommandEvent& event ) override;
87 void OnShowConsoleChanged( wxCommandEvent& event ) override;
88 void OnIdle( wxIdleEvent& event ) override;
89
90 void pluginInit();
92 BOM_GENERATOR_HANDLER* addGenerator( const wxString& aPath,
93 const wxString& aName = wxEmptyString );
94 bool pluginExists( const wxString& aName );
95
97 {
98 int idx = m_lbGenerators->GetSelection();
99
100 if( idx < 0 || idx >= (int)m_generators.size() )
101 return nullptr;
102
103 return m_generators[idx].get();
104 }
105
106 wxString chooseGenerator();
107};
108
109
110// Create and show DIALOG_BOM.
112{
113 DIALOG_BOM dlg( aCaller );
114
115 // QuasiModal so syntax help works
116 return dlg.ShowQuasiModal();
117}
118
119
121 DIALOG_BOM_BASE( parent ),
122 m_parent( parent ),
123 m_initialized( false ),
124 m_helpWindow( nullptr )
125{
126 m_buttonAddGenerator->SetBitmap( KiBitmapBundle( BITMAPS::small_plus ) );
127 m_buttonDelGenerator->SetBitmap( KiBitmapBundle( BITMAPS::small_trash ) );
128 m_buttonEdit->SetBitmap( KiBitmapBundle( BITMAPS::small_edit ) );
129
131
132#ifndef __WINDOWS__
133 m_checkBoxShowConsole->Show( false );
134#endif
135
136 SetupStandardButtons( { { wxID_OK, _( "Generate" ) },
137 { wxID_CANCEL, _( "Close" ) } } );
138
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;
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, wxS( "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
235BOM_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->IsOk() )
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
259bool 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
271void 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
327void 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 bool status = false;
347
348 if( m_parent->ReadyToNetlist( _( "Generating BOM requires a fully annotated schematic." ) ) )
349 status = m_parent->WriteNetListFile( NET_TYPE_BOM, fullfilename, GNL_OPT_BOM|GNL_ALL, &reporter );
350
351 if( !status )
352 DisplayError( this, _( "Failed to create file." ) );
353
354 m_Messages->SetValue( reportmsg );
355
356 // Force focus back on the dialog
357 SetFocus();
358}
359
360
361void DIALOG_BOM::OnRemoveGenerator( wxCommandEvent& event )
362{
363 int ii = m_lbGenerators->GetSelection();
364
365 if( ii < 0 )
366 return;
367
368 m_lbGenerators->Delete( ii );
369 m_generators.erase( m_generators.begin() + ii );
370
371 // Select the next item, if exists
372 if( m_lbGenerators->GetCount() )
373 m_lbGenerators->SetSelection( std::min( ii, (int) m_lbGenerators->GetCount() - 1 ) );
374
375 pluginInit();
376}
377
378
379void DIALOG_BOM::OnAddGenerator( wxCommandEvent& event )
380{
381 wxString filename = chooseGenerator();
382
383 if( filename.IsEmpty() )
384 return;
385
386 // Creates a new plugin entry
387 wxFileName fn( filename );
388 wxString name = wxGetTextFromUser( _( "Generator nickname:" ), _( "Add Generator" ),
389 fn.GetName(), this );
390
391 if( name.IsEmpty() )
392 return;
393
394 // Verify if it does not exists
395 if( pluginExists( name ) )
396 {
397 wxMessageBox( wxString::Format( _( "Nickname '%s' already in use." ), name ) );
398 return;
399 }
400
401 try
402 {
403 auto plugin = addGenerator( fn.GetFullPath(), name );
404
405 if( plugin )
406 {
407 m_lbGenerators->SetSelection( m_lbGenerators->GetCount() - 1 );
408 m_textCtrlCommand->SetValue( plugin->GetCommand() );
409 pluginInit();
410 }
411 }
412 catch( const std::runtime_error& e )
413 {
414 DisplayError( this, e.what() );
415 }
416}
417
418
420{
421 static wxString lastPath;
422
423 if( lastPath.IsEmpty() )
424 lastPath = PATHS::GetUserPluginsPath();
425
426 wxString fullFileName = wxFileSelector( _( "Generator File" ), lastPath, wxEmptyString,
427 wxEmptyString, wxFileSelectorDefaultWildcardStr,
428 wxFD_OPEN, this );
429
430 return fullFileName;
431}
432
433
434void DIALOG_BOM::OnEditGenerator( wxCommandEvent& event )
435{
436 auto plugin = selectedGenerator();
437
438 if( !plugin )
439 return;
440
441 wxString pluginFile = plugin->GetFile().GetFullPath();
442
443 if( pluginFile.Length() <= 2 ) // if name != ""
444 {
445 wxMessageBox( _( "Generator file name not found." ) );
446 return;
447 }
448
449 wxString editorname = Pgm().GetTextEditor();
450
451 if( !editorname.IsEmpty() )
452 ExecuteFile( editorname, pluginFile );
453 else
454 wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
455}
456
457
458void DIALOG_BOM::OnHelp( wxCommandEvent& event )
459{
460 if( m_helpWindow )
461 {
463 return;
464 }
465
466 m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Bill of Materials Generation Help" ) );
467 m_helpWindow->SetDialogSizeInDU( 500, 350 );
468
469 wxString html_txt;
470 ConvertMarkdown2Html( wxGetTranslation( s_bomHelpInfo ), html_txt );
471
472 m_helpWindow->AddHTML_Text( html_txt );
474}
475
476
477void DIALOG_BOM::OnCommandLineEdited( wxCommandEvent& event )
478{
479 auto generator = selectedGenerator();
480
481 if( generator )
482 generator->SetCommand( m_textCtrlCommand->GetValue() );
483}
484
485
486void DIALOG_BOM::OnNameEdited( wxCommandEvent& event )
487{
488 if( m_textCtrlName->GetValue().IsEmpty() )
489 return;
490
491 int ii = m_lbGenerators->GetSelection();
492
493 if( ii < 0 )
494 return;
495
496 m_generators[ii]->SetName( m_textCtrlName->GetValue() );
497 m_lbGenerators->SetString( ii, m_generators[ii]->GetName() );
498}
499
500
501void DIALOG_BOM::OnShowConsoleChanged( wxCommandEvent& event )
502{
503#ifdef __WINDOWS__
504 static constexpr wxChar OPT_SHOW_CONSOLE[] = wxT( "show_console" );
505
506 auto plugin = selectedGenerator();
507
508 if( !plugin )
509 return;
510
511 if( m_checkBoxShowConsole->IsChecked() )
512 {
513 if( plugin->Options().Index( OPT_SHOW_CONSOLE ) == wxNOT_FOUND )
514 plugin->Options().Add( OPT_SHOW_CONSOLE );
515 }
516 else
517 {
518 plugin->Options().Remove( OPT_SHOW_CONSOLE );
519 }
520#endif
521}
522
523
524void DIALOG_BOM::OnIdle( wxIdleEvent& event )
525{
526 // On some platforms we initialize wxTextCtrls to all-selected, but we don't want that
527 // for the messages text box.
528 if( !m_initialized )
529 {
530 m_Messages->SetSelection( 0, 0 );
531 m_initialized = true;
532 }
533}
const char * name
Definition: DXF_plotter.cpp:57
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap)
Definition: bitmap.cpp:110
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:434
SCH_EDIT_FRAME * m_parent
Definition: dialog_bom.cpp:68
void OnRemoveGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:361
void installGeneratorsList()
Definition: dialog_bom.cpp:186
void pluginInit()
Definition: dialog_bom.cpp:277
bool m_initialized
Definition: dialog_bom.cpp:70
BOM_GENERATOR_HANDLER * selectedGenerator()
Definition: dialog_bom.cpp:96
bool pluginExists(const wxString &aName)
Definition: dialog_bom.cpp:259
BOM_GENERATOR_HANDLER * addGenerator(const wxString &aPath, const wxString &aName=wxEmptyString)
Definition: dialog_bom.cpp:235
void OnIdle(wxIdleEvent &event) override
Definition: dialog_bom.cpp:524
void OnHelp(wxCommandEvent &event) override
Definition: dialog_bom.cpp:458
void OnCommandLineEdited(wxCommandEvent &event) override
Definition: dialog_bom.cpp:477
wxString chooseGenerator()
Definition: dialog_bom.cpp:419
void OnGeneratorSelected(wxCommandEvent &event) override
Definition: dialog_bom.cpp:271
void OnNameEdited(wxCommandEvent &event) override
Definition: dialog_bom.cpp:486
void OnRunGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:327
HTML_MESSAGE_BOX * m_helpWindow
Definition: dialog_bom.cpp:72
void OnShowConsoleChanged(wxCommandEvent &event) override
Definition: dialog_bom.cpp:501
BOM_GENERATOR_ARRAY m_generators
Definition: dialog_bom.cpp:69
DIALOG_BOM(SCH_EDIT_FRAME *parent)
Definition: dialog_bom.cpp:120
void OnAddGenerator(wxCommandEvent &event) override
Definition: dialog_bom.cpp:379
void SetInitialFocus(wxWindow *aWindow)
Sets the window (usually a wxTextCtrl) that should be focused when the dialog is shown.
Definition: dialog_shim.h:102
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:54
static wxString GetStockPluginsPath()
Gets the stock (install) plugins path.
Definition: paths.cpp:267
virtual const wxString & GetTextEditor(bool aCanShowFileChooser=true)
Return the path to the preferred text editor application.
Definition: pgm_base.cpp:195
wxString GetFileName() const override
Helper to retrieve the filename from the root sheet screen.
Definition: schematic.cpp:283
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 wxBitmapBundle &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:161
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:111
wxString s_bomHelpInfo
Definition: dialog_bom.cpp:55
std::vector< std::unique_ptr< BOM_GENERATOR_HANDLER > > BOM_GENERATOR_ARRAY
Definition: dialog_bom.cpp:61
#define _(s)
int ExecuteFile(const wxString &aEditorName, const wxString &aFileName, wxProcess *aCallback, bool aFileForKicad)
Call the executable file aEditorName with the parameter aFileName.
Definition: gestfich.cpp:139
Some functions to handle hotkeys in KiCad.
@ NET_TYPE_BOM
Definition: netlist.h:37
#define GNL_ALL
@ GNL_OPT_BOM
PGM_BASE & Pgm()
The global Program "get" accessor.
Definition: pgm_base.cpp:1059
see class PGM_BASE
void ConvertMarkdown2Html(const wxString &aMarkdownInput, wxString &aHtmlOutput)
std::vector< BOM_PLUGIN_SETTINGS > plugins