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 The 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, see <https://www.gnu.org/licenses/>.
19 */
20
25
26
27#include <bitmaps.h>
29#include <bom_plugins.h>
30#include <confirm.h>
31#include <dialog_bom.h>
33#include <eeschema_settings.h>
34#include <gestfich.h>
35#include <i18n_utility.h> // for _HKI definition used in dialog_bom_help_md.h
36#include <invoke_sch_dialog.h>
37#include <kiface_base.h>
38#include <netlist.h>
40#include <paths.h>
41#include <pgm_base.h>
42#include <reporter.h>
43#include <sch_edit_frame.h>
44#include <string_utils.h>
45
46#include <wx/filedlg.h>
47#include <wx/log.h>
48#include <wx/msgdlg.h>
49#include <wx/textdlg.h>
50
51wxString s_bomHelpInfo =
52#include <dialog_bom_help_md.h>
53;
54
55// Create and show DIALOG_BOM.
57{
58 DIALOG_BOM dlg( aCaller );
59
60 // QuasiModal so syntax help works
61 return dlg.ShowQuasiModal();
62}
63
64
66 DIALOG_BOM_BASE( parent ),
67 m_parent( parent ),
68 m_initialized( false ),
69 m_helpWindow( nullptr )
70{
74
76
77#ifndef __WINDOWS__
78 m_checkBoxShowConsole->Show( false );
79#endif
80
81 SetupStandardButtons( { { wxID_OK, _( "Generate" ) },
82 { wxID_CANCEL, _( "Close" ) } } );
83
85
86 // Now all widgets have the size fixed, call FinishDialogSettings
88
89 m_buttonReset->Bind( wxEVT_BUTTON,
90 [&]( wxCommandEvent& )
91 {
92 EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
93
94 cfg->m_BomPanel.selected_plugin = wxEmptyString;
96
98 } );
99}
100
101
103{
104 if( m_helpWindow )
105 m_helpWindow->Destroy();
106
107 EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
108
109 cfg->m_BomPanel.plugins.clear();
110
111 for( const std::unique_ptr<BOM_GENERATOR_HANDLER>& plugin : m_generators )
112 {
113 wxString name = plugin->GetName();
114 wxFileName path( plugin->GetStoredPath() );
115
116 // handle empty nickname by stripping path
117 if( name.IsEmpty() )
118 name = path.GetName();
119
120 EESCHEMA_SETTINGS::BOM_PLUGIN_SETTINGS setting( name, path.GetFullPath() );
121 setting.command = plugin->GetCommand();
122
123 cfg->m_BomPanel.plugins.emplace_back( setting );
124 }
125
126 cfg->m_BomPanel.selected_plugin = m_lbGenerators->GetStringSelection().ToStdString();
127}
128
129
130// Read the initialized plugins in config and fill the list of names
132{
133 EESCHEMA_SETTINGS* cfg = m_parent->eeconfig();
134
135 wxString active_plugin_name = cfg->m_BomPanel.selected_plugin;
136
137 m_generators.clear();
138
140 {
141 auto plugin = std::make_unique<BOM_GENERATOR_HANDLER>( setting.path );
142
143 plugin->SetName( setting.name );
144
145 if( !setting.command.IsEmpty() )
146 plugin->SetCommand( setting.command );
147
148 m_generators.emplace_back( std::move( plugin ) );
149 }
150
151 m_lbGenerators->Clear();
152
153 if( !m_generators.empty() )
154 {
155 for( unsigned ii = 0; ii < m_generators.size(); ii++ )
156 {
157 wxString name = m_generators[ii]->GetName();
158
159 if( !m_generators[ii]->FindFilePath().Exists( wxFILE_EXISTS_REGULAR ) )
160 {
161 wxLogTrace( BOM_TRACE, wxS( "BOM plugin %s not found" ),
162 m_generators[ii]->FindFilePath().GetFullName() );
163 name.Append( wxT( " " ) + _( "(file missing)" ) );
164
165 if( active_plugin_name == name )
166 active_plugin_name.Clear();
167 }
168
169 m_lbGenerators->Append( name );
170
171 if( active_plugin_name == name )
172 m_lbGenerators->SetSelection( ii );
173 }
174 }
175
176 pluginInit();
177}
178
179
180BOM_GENERATOR_HANDLER* DIALOG_BOM::addGenerator( const wxString& aPath, const wxString& aName )
181{
182 BOM_GENERATOR_HANDLER* ret = nullptr;
183 auto plugin = std::make_unique<BOM_GENERATOR_HANDLER>( aPath );
184
185 if( !plugin->IsOk() )
186 return nullptr;
187
188 if( !aName.IsEmpty() )
189 {
190 plugin->SetName( aName );
191 m_lbGenerators->Append( aName );
192 }
193 else
194 {
195 m_lbGenerators->Append( plugin->GetName() );
196 }
197
198 ret = plugin.get();
199 m_generators.push_back( std::move( plugin ) );
200 return ret;
201}
202
203
204bool DIALOG_BOM::pluginExists( const wxString& aName )
205{
206 for( unsigned ii = 0; ii < m_generators.size(); ii++ )
207 {
208 if( aName == m_generators[ii]->GetName() )
209 return true;
210 }
211
212 return false;
213}
214
215
216void DIALOG_BOM::OnGeneratorSelected( wxCommandEvent& event )
217{
218 pluginInit();
219}
220
221
223{
225
226 if( !plugin )
227 {
228 m_textCtrlName->SetValue( wxEmptyString );
229 m_textCtrlCommand->SetValue( wxEmptyString );
230 m_Messages->SetValue( wxEmptyString );
231 return;
232 }
233
234 if( !plugin->FindFilePath().Exists( wxFILE_EXISTS_REGULAR ) )
235 {
236 m_textCtrlName->SetValue( wxEmptyString );
237 m_textCtrlCommand->SetValue( wxEmptyString );
238
239 wxString msg =
240 wxString::Format( _( "The selected BOM generator script %s could not be found." ),
241 plugin->GetFile().GetFullPath() );
242
243 if( !plugin->GetFile().IsAbsolute() )
244 {
245 msg.Append( wxString::Format( _( "\n\nSearched:\n\t%s\n\t%s" ),
248 }
249
250 m_Messages->SetValue( msg );
251 return;
252 }
253
254 m_textCtrlName->SetValue( plugin->GetName() );
255 m_textCtrlCommand->SetValue( plugin->GetCommand() );
256 m_Messages->SetValue( plugin->GetInfo() );
257 m_Messages->SetSelection( 0, 0 );
258
259#ifdef __WINDOWS__
260 if( plugin->Options().Index( wxT( "show_console" ) ) == wxNOT_FOUND )
261 m_checkBoxShowConsole->SetValue( false );
262 else
263 m_checkBoxShowConsole->SetValue( true );
264#endif
265
266 // A plugin can be not working, so do not left the OK button enabled if
267 // the plugin is not ready to use
268 m_sdbSizerOK->Enable( plugin->IsOk() );
269}
270
271
272void DIALOG_BOM::OnRunGenerator( wxCommandEvent& event )
273{
274 // Calculate the xml netlist filename
275 wxFileName fn = m_parent->Schematic().GetFileName();
276
277 fn.ClearExt();
278
279 wxString fullfilename = fn.GetFullPath();
280 m_parent->ClearMsgPanel();
281
283 m_parent->SetNetListerCommand( m_textCtrlCommand->GetValue() );
284
285#ifdef __WINDOWS__
286 if( m_checkBoxShowConsole->IsChecked() )
287 m_parent->SetExecFlags( wxEXEC_SHOW_CONSOLE );
288#endif
289
290 bool status = false;
291
292 if( m_parent->ReadyToNetlist( _( "Generating BOM requires a fully annotated schematic." ) ) )
293 status = m_parent->WriteNetListFile( NET_TYPE_BOM, fullfilename,
295
296 if( !status )
297 DisplayErrorMessage( this, _( "Failed to create file." ) );
298
299 m_Messages->SetValue( reporter.GetMessages() );
300
301 // Force focus back on the dialog
302 SetFocus();
303}
304
305
306void DIALOG_BOM::OnRemoveGenerator( wxCommandEvent& event )
307{
308 int ii = m_lbGenerators->GetSelection();
309
310 if( ii < 0 )
311 return;
312
313 m_lbGenerators->Delete( ii );
314 m_generators.erase( m_generators.begin() + ii );
315
316 // Select the next item, if exists
317 if( m_lbGenerators->GetCount() )
318 m_lbGenerators->SetSelection( std::min( ii, (int) m_lbGenerators->GetCount() - 1 ) );
319
320 pluginInit();
321}
322
323
324void DIALOG_BOM::OnAddGenerator( wxCommandEvent& event )
325{
326 wxString filename = chooseGenerator();
327
328 if( filename.IsEmpty() )
329 return;
330
331 // Creates a new plugin entry
332 wxFileName fn( filename );
333 wxString name = wxGetTextFromUser( _( "Generator nickname:" ), _( "Add Generator" ),
334 fn.GetName(), this );
335
336 if( name.IsEmpty() )
337 return;
338
339 // Verify if it does not exists
340 if( pluginExists( name ) )
341 {
342 wxMessageBox( wxString::Format( _( "Nickname '%s' already in use." ), name ) );
343 return;
344 }
345
346 try
347 {
348 auto plugin = addGenerator( fn.GetFullPath(), name );
349
350 if( plugin )
351 {
352 m_lbGenerators->SetSelection( m_lbGenerators->GetCount() - 1 );
353 m_textCtrlCommand->SetValue( plugin->GetCommand() );
354 pluginInit();
355 }
356 }
357 catch( const std::runtime_error& e )
358 {
359 DisplayErrorMessage( this, e.what() );
360 }
361}
362
363
365{
366 static wxString lastPath;
367
368 if( lastPath.IsEmpty() )
369 lastPath = PATHS::GetUserPluginsPath();
370
371 wxString fullFileName = wxFileSelector( _( "Generator File" ), lastPath, wxEmptyString,
372 wxEmptyString, wxFileSelectorDefaultWildcardStr,
373 wxFD_OPEN, this );
374
375 return fullFileName;
376}
377
378
379void DIALOG_BOM::OnEditGenerator( wxCommandEvent& event )
380{
381 auto plugin = selectedGenerator();
382
383 if( !plugin )
384 return;
385
386 wxString pluginFile = plugin->GetFile().GetFullPath();
387
388 if( pluginFile.Length() <= 2 ) // if name != ""
389 {
390 wxMessageBox( _( "Generator file name not found." ) );
391 return;
392 }
393
394 wxString editorname = Pgm().GetTextEditor();
395
396 if( !editorname.IsEmpty() )
397 ExecuteFile( editorname, pluginFile );
398 else
399 wxMessageBox( _( "No text editor selected in KiCad. Please choose one." ) );
400}
401
402
403void DIALOG_BOM::OnHelp( wxCommandEvent& event )
404{
405 if( m_helpWindow )
406 {
407 m_helpWindow->ShowModeless();
408 return;
409 }
410
411 m_helpWindow = new HTML_MESSAGE_BOX( nullptr, _( "Bill of Materials Generation Help" ) );
412 m_helpWindow->SetDialogSizeInDU( 500, 350 );
413
414 wxString html_txt;
415 ConvertMarkdown2Html( wxGetTranslation( s_bomHelpInfo ), html_txt );
416
417 m_helpWindow->AddHTML_Text( html_txt );
418 m_helpWindow->ShowModeless();
419}
420
421
422void DIALOG_BOM::OnCommandLineEdited( wxCommandEvent& event )
423{
424 auto generator = selectedGenerator();
425
426 if( generator )
427 generator->SetCommand( m_textCtrlCommand->GetValue() );
428}
429
430
431void DIALOG_BOM::OnNameEdited( wxCommandEvent& event )
432{
433 if( m_textCtrlName->GetValue().IsEmpty() )
434 return;
435
436 int ii = m_lbGenerators->GetSelection();
437
438 if( ii < 0 )
439 return;
440
441 m_generators[ii]->SetName( m_textCtrlName->GetValue() );
442 m_lbGenerators->SetString( ii, m_generators[ii]->GetName() );
443}
444
445
446void DIALOG_BOM::OnShowConsoleChanged( wxCommandEvent& event )
447{
448#ifdef __WINDOWS__
449 static constexpr wxChar OPT_SHOW_CONSOLE[] = wxT( "show_console" );
450
451 auto plugin = selectedGenerator();
452
453 if( !plugin )
454 return;
455
456 if( m_checkBoxShowConsole->IsChecked() )
457 {
458 if( plugin->Options().Index( OPT_SHOW_CONSOLE ) == wxNOT_FOUND )
459 plugin->Options().Add( OPT_SHOW_CONSOLE );
460 }
461 else
462 {
463 plugin->Options().Remove( OPT_SHOW_CONSOLE );
464 }
465#endif
466}
467
468
469void DIALOG_BOM::OnIdle( wxIdleEvent& event )
470{
471 // On some platforms we initialize wxTextCtrls to all-selected, but we don't want that
472 // for the messages text box.
473 if( !m_initialized )
474 {
475 m_Messages->SetSelection( 0, 0 );
476 m_initialized = true;
477 }
478}
479
480
482{
483 int idx = m_lbGenerators->GetSelection();
484
485 if( idx < 0 || idx >= (int) m_generators.size() )
486 return nullptr;
487
488 return m_generators[idx].get();
489}
const char * name
wxBitmapBundle KiBitmapBundle(BITMAPS aBitmap, int aMinHeight)
Definition bitmap.cpp:106
const wxChar BOM_TRACE[]
Bill of material output generator.
Definition bom_plugins.h:42
const wxString & GetInfo() const
Return plugin description stored in the plugin header file (if available).
Definition bom_plugins.h:64
const wxFileName & GetFile() const
Return the file name of the plugin.
Definition bom_plugins.h:72
bool IsOk()
Return true if the plugin is ready to work, i.e.
Definition bom_plugins.h:52
wxArrayString & Options()
Accessor to array of options.
const wxString & GetName() const
Return the customisable plugin name.
Definition bom_plugins.h:92
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.
wxButton * m_buttonReset
STD_BITMAP_BUTTON * m_buttonEdit
wxTextCtrl * m_textCtrlName
STD_BITMAP_BUTTON * m_buttonAddGenerator
STD_BITMAP_BUTTON * m_buttonDelGenerator
DIALOG_BOM_BASE(wxWindow *parent, wxWindowID id=wxID_ANY, const wxString &title=_("Bill of Materials"), const wxPoint &pos=wxDefaultPosition, const wxSize &size=wxSize(-1,-1), long style=wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER)
wxTextCtrl * m_Messages
wxCheckBox * m_checkBoxShowConsole
wxTextCtrl * m_textCtrlCommand
wxListBox * m_lbGenerators
wxButton * m_sdbSizerOK
void OnEditGenerator(wxCommandEvent &event) override
SCH_EDIT_FRAME * m_parent
Definition dialog_bom.h:38
void OnRemoveGenerator(wxCommandEvent &event) override
void installGeneratorsList()
void pluginInit()
bool m_initialized
Definition dialog_bom.h:40
BOM_GENERATOR_HANDLER * selectedGenerator()
bool pluginExists(const wxString &aName)
BOM_GENERATOR_HANDLER * addGenerator(const wxString &aPath, const wxString &aName=wxEmptyString)
void OnIdle(wxIdleEvent &event) override
void OnHelp(wxCommandEvent &event) override
void OnCommandLineEdited(wxCommandEvent &event) override
wxString chooseGenerator()
void OnGeneratorSelected(wxCommandEvent &event) override
void OnNameEdited(wxCommandEvent &event) override
void OnRunGenerator(wxCommandEvent &event) override
HTML_MESSAGE_BOX * m_helpWindow
Definition dialog_bom.h:42
void OnShowConsoleChanged(wxCommandEvent &event) override
BOM_GENERATOR_ARRAY m_generators
Definition dialog_bom.h:39
DIALOG_BOM(SCH_EDIT_FRAME *parent)
void OnAddGenerator(wxCommandEvent &event) override
void SetInitialFocus(wxWindow *aWindow)
Sets the window (usually a wxTextCtrl) that should be focused when the dialog is shown.
Definition dialog_shim.h:79
void SetupStandardButtons(std::map< int, wxString > aLabels={})
void finishDialogSettings()
In all dialogs, we must call the same functions to fix minimal dlg size, the default position and per...
static std::vector< BOM_PLUGIN_SETTINGS > DefaultBomPlugins()
static wxString GetUserPluginsPath()
Gets the user path for plugins.
Definition paths.cpp:49
static wxString GetStockPluginsPath()
Gets the stock (install) plugins path.
Definition paths.cpp:377
virtual const wxString & GetTextEditor(bool aCanShowFileChooser=true)
Return the path to the preferred text editor application.
Definition pgm_base.cpp:218
Schematic editor (Eeschema) main window.
A wrapper for reporting to a wxString object.
Definition reporter.h:189
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition confirm.cpp:217
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.
wxString s_bomHelpInfo
#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:160
Some functions to handle hotkeys in KiCad.
#define GNL_ALL
@ GNL_OPT_BOM
PGM_BASE & Pgm()
The global program "get" accessor.
see class PGM_BASE
void ConvertMarkdown2Html(const wxString &aMarkdownInput, wxString &aHtmlOutput)
std::vector< BOM_PLUGIN_SETTINGS > plugins
std::string path
IbisParser parser & reporter