KiCad PCB EDA Suite
panel_fp_lib_table.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) 2012 SoftPLC Corporation, Dick Hollenbeck <dick@softplc.com>
5  * Copyright (C) 2013 CERN
6  * Copyright (C) 2012-2020 KiCad Developers, see AUTHORS.txt for contributors.
7  *
8  * This program is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU General Public License
10  * as published by the Free Software Foundation; either version 2
11  * of the License, or (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program; if not, you may find one here:
20  * http://www.gnu.org/licenses/old-licenses/gpl-2.0.html
21  * or you may search the http://www.gnu.org website for the version 2 license,
22  * or you may write to the Free Software Foundation, Inc.,
23  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
24  */
25 
26 
27 /* TODO:
28 
29 *) After any change to uri, reparse the environment variables.
30 
31 */
32 
33 
34 #include <set>
35 #include <wx/regex.h>
36 #include <wx/grid.h>
37 
38 #include <project.h>
39 #include <3d_viewer/eda_3d_viewer.h> // for KISYS3DMOD
40 #include <panel_fp_lib_table.h>
41 #include <lib_id.h>
42 #include <fp_lib_table.h>
43 #include <lib_table_lexer.h>
44 #include <invoke_pcb_dialog.h>
45 #include <bitmaps.h>
46 #include <grid_tricks.h>
47 #include <widgets/wx_grid.h>
48 #include <confirm.h>
49 #include <lib_table_grid.h>
51 #include <pgm_base.h>
52 #include <pcb_edit_frame.h>
53 #include <env_paths.h>
55 #include <footprint_viewer_frame.h>
56 #include <footprint_edit_frame.h>
57 #include <kiway.h>
60 #include <pcbnew_id.h> // For ID_PCBNEW_END_LIST
62 
63 // clang-format off
64 
68 struct supportedFileType
69 {
70  wxString m_Description;
71  wxString m_FileFilter;
72  wxString m_FolderSearchExtension;
73  bool m_IsFile;
75 };
76 
80 enum {
85 };
86 
92 static const std::map<int, supportedFileType>& fileTypes()
93 {
94  /*
95  * TODO(C++20): Clean this up
96  * This is wrapped inside a function to prevent a static initialization order fiasco
97  * with the file extension variables. Once C++20 is allowed in KiCad code, those file
98  * extensions can be made constexpr and this can be removed from a function call and
99  * placed in the file normally.
100  */
101  static const std::map<int, supportedFileType> fileTypes =
102  {
104  {
105  "KiCad (folder with .kicad_mod files)", "", KiCadFootprintFileExtension,
106  false, IO_MGR::KICAD_SEXP
107  }
108  },
110  {
111  "Eagle 6.x (*.lbr)", EagleFootprintLibPathWildcard(), "", true, IO_MGR::EAGLE
112  }
113  },
115  {
116  "KiCad legacy (*.mod)", LegacyFootprintLibPathWildcard(), "", true, IO_MGR::LEGACY
117  }
118  },
120  {
121  "Geda (folder with *.fp files)", "", GedaPcbFootprintLibFileExtension, false,
123  }
124  },
125  };
126 
127  return fileTypes;
128 }
129 // clang-format on
130 
131 
136 class LIBRARY_TRAVERSER : public wxDirTraverser
137 {
138 public:
139  LIBRARY_TRAVERSER( wxString aSearchExtension, wxString aInitialDir )
140  : m_searchExtension( aSearchExtension ),
141  m_currentDir( aInitialDir )
142  {
143  }
144 
145 
146  virtual wxDirTraverseResult OnFile( const wxString& aFileName ) override
147  {
148  wxFileName file( aFileName );
149  if( m_searchExtension.IsSameAs( file.GetExt(), false ) )
150  {
151  m_foundDirs.insert( { m_currentDir, 1 } );
152  }
153 
154  return wxDIR_CONTINUE;
155  }
156 
157 
158  virtual wxDirTraverseResult OnOpenError( const wxString& aOpenErrorName ) override
159  {
160  m_failedDirs.insert( { aOpenErrorName, 1 } );
161  return wxDIR_IGNORE;
162  }
163 
164 
166  {
167  return m_failedDirs.size() > 0;
168  }
169 
170 
171  virtual wxDirTraverseResult OnDir( const wxString& aDirName ) override
172  {
173  m_currentDir = aDirName;
174  return wxDIR_CONTINUE;
175  }
176 
177 
178  void GetPaths( wxArrayString& aPathArray )
179  {
180  for( auto foundDirsPair : m_foundDirs )
181  {
182  aPathArray.Add( foundDirsPair.first );
183  }
184  }
185 
186 
187  void GetFailedPaths( wxArrayString& aPathArray )
188  {
189  for( auto failedDirsPair : m_failedDirs )
190  {
191  aPathArray.Add( failedDirsPair.first );
192  }
193  }
194 
195 private:
197  wxString m_currentDir;
198  std::unordered_map<wxString, int> m_foundDirs;
199  std::unordered_map<wxString, int> m_failedDirs;
200 };
201 
202 
207 {
208  friend class PANEL_FP_LIB_TABLE;
209  friend class FP_GRID_TRICKS;
210 
211 protected:
212  LIB_TABLE_ROW* at( size_t aIndex ) override { return &rows.at( aIndex ); }
213 
214  size_t size() const override { return rows.size(); }
215 
217  {
218  return dynamic_cast< LIB_TABLE_ROW* >( new FP_LIB_TABLE_ROW );
219  }
220 
221  LIB_TABLE_ROWS_ITER begin() override { return rows.begin(); }
222 
224  {
225  return rows.insert( aIterator, aRow );
226  }
227 
228  void push_back( LIB_TABLE_ROW* aRow ) override { rows.push_back( aRow ); }
229 
231  {
232  return rows.erase( aFirst, aLast );
233  }
234 
235 public:
236 
237  FP_LIB_TABLE_GRID( const FP_LIB_TABLE& aTableToEdit )
238  {
239  rows = aTableToEdit.rows;
240  }
241 };
242 
243 
244 #define MYID_OPTIONS_EDITOR 15151
245 
246 
248 {
249 public:
251  GRID_TRICKS( aGrid ),
252  m_dialog( aParent )
253  { }
254 
255 protected:
257 
258  void optionsEditor( int aRow )
259  {
260  FP_LIB_TABLE_GRID* tbl = (FP_LIB_TABLE_GRID*) m_grid->GetTable();
261 
262  if( tbl->GetNumberRows() > aRow )
263  {
264  LIB_TABLE_ROW* row = tbl->at( (size_t) aRow );
265  const wxString& options = row->GetOptions();
266  wxString result = options;
267 
268  InvokePluginOptionsEditor( m_dialog, row->GetNickName(), row->GetType(), options,
269  &result );
270 
271  if( options != result )
272  {
273  row->SetOptions( result );
274  m_grid->Refresh();
275  }
276  }
277  }
278 
279  bool handleDoubleClick( wxGridEvent& aEvent ) override
280  {
281  if( aEvent.GetCol() == COL_OPTIONS )
282  {
283  optionsEditor( aEvent.GetRow() );
284  return true;
285  }
286 
287  return false;
288  }
289 
290  void showPopupMenu( wxMenu& menu ) override
291  {
292  if( m_grid->GetGridCursorCol() == COL_OPTIONS )
293  {
294  menu.Append( MYID_OPTIONS_EDITOR, _( "Options Editor..." ), _( "Edit options" ) );
295  menu.AppendSeparator();
296  }
297 
299  }
300 
301  void doPopupSelection( wxCommandEvent& event ) override
302  {
303  if( event.GetId() == MYID_OPTIONS_EDITOR )
304  optionsEditor( m_grid->GetGridCursorRow() );
305  else
307  }
308 
311  void paste_text( const wxString& cb_text ) override
312  {
313  FP_LIB_TABLE_GRID* tbl = (FP_LIB_TABLE_GRID*) m_grid->GetTable();
314  size_t ndx = cb_text.find( "(fp_lib_table" );
315 
316  if( ndx != std::string::npos )
317  {
318  // paste the FP_LIB_TABLE_ROWs of s-expression (fp_lib_table), starting
319  // at column 0 regardless of current cursor column.
320 
321  STRING_LINE_READER slr( TO_UTF8( cb_text ), "Clipboard" );
322  LIB_TABLE_LEXER lexer( &slr );
323  FP_LIB_TABLE tmp_tbl;
324  bool parsed = true;
325 
326  try
327  {
328  tmp_tbl.Parse( &lexer );
329  }
330  catch( PARSE_ERROR& pe )
331  {
332  DisplayError( m_dialog, pe.What() );
333  parsed = false;
334  }
335 
336  if( parsed )
337  {
338  // make sure the table is big enough...
339  if( tmp_tbl.GetCount() > (unsigned) tbl->GetNumberRows() )
340  tbl->AppendRows( tmp_tbl.GetCount() - tbl->GetNumberRows() );
341 
342  for( unsigned i = 0; i < tmp_tbl.GetCount(); ++i )
343  tbl->rows.replace( i, tmp_tbl.At( i ).clone() );
344  }
345 
346  m_grid->AutoSizeColumns( false );
347  }
348  else
349  {
350  // paste spreadsheet formatted text.
351  GRID_TRICKS::paste_text( cb_text );
352 
353  m_grid->AutoSizeColumns( false );
354  }
355  }
356 };
357 
358 
360  FP_LIB_TABLE* aGlobal, const wxString& aGlobalTblPath,
361  FP_LIB_TABLE* aProject, const wxString& aProjectTblPath,
362  const wxString& aProjectBasePath ) :
363  PANEL_FP_LIB_TABLE_BASE( aParent ),
364  m_global( aGlobal ),
365  m_project( aProject ),
366  m_projectBasePath( aProjectBasePath ),
367  m_parent( aParent )
368 {
369  // For user info, shows the table filenames:
370  m_GblTableFilename->SetLabel( aGlobalTblPath );
371 
372  m_global_grid->SetTable( new FP_LIB_TABLE_GRID( *aGlobal ), true );
373 
374  // add Cut, Copy, and Paste to wxGrids
375  m_path_subs_grid->PushEventHandler( new GRID_TRICKS( m_path_subs_grid ) );
376 
377  wxArrayString choices;
378 
379  choices.Add( IO_MGR::ShowType( IO_MGR::KICAD_SEXP ) );
380 #if defined(BUILD_GITHUB_PLUGIN)
381  choices.Add( IO_MGR::ShowType( IO_MGR::GITHUB ) );
382 #endif
383  choices.Add( IO_MGR::ShowType( IO_MGR::LEGACY ) );
384  choices.Add( IO_MGR::ShowType( IO_MGR::EAGLE ) );
385  choices.Add( IO_MGR::ShowType( IO_MGR::GEDA_PCB ) );
386 
387  /* PCAD_PLUGIN does not support Footprint*() functions
388  choices.Add( IO_MGR::ShowType( IO_MGR::PCAD ) );
389  */
390  PCBNEW_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings<PCBNEW_SETTINGS>();
391 
392  if( cfg->m_lastFootprintLibDir.IsEmpty() )
394 
395  auto setupGrid =
396  [&]( WX_GRID* aGrid )
397  {
398  // Give a bit more room for wxChoice editors
399  aGrid->SetDefaultRowSize( aGrid->GetDefaultRowSize() + 4 );
400 
401  // add Cut, Copy, and Paste to wxGrids
402  aGrid->PushEventHandler( new FP_GRID_TRICKS( m_parent, aGrid ) );
403 
404  aGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
405  aGrid->AutoSizeColumns( false );
406 
407  wxGridCellAttr* attr;
408 
409  attr = new wxGridCellAttr;
410  attr->SetEditor( new GRID_CELL_PATH_EDITOR( m_parent, &cfg->m_lastFootprintLibDir,
411  wxEmptyString, true, m_projectBasePath ) );
412  aGrid->SetColAttr( COL_URI, attr );
413 
414  attr = new wxGridCellAttr;
415  attr->SetEditor( new wxGridCellChoiceEditor( choices ) );
416  aGrid->SetColAttr( COL_TYPE, attr );
417 
418  attr = new wxGridCellAttr;
419  attr->SetRenderer( new wxGridCellBoolRenderer() );
420  attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
421  aGrid->SetColAttr( COL_ENABLED, attr );
422 
423  // all but COL_OPTIONS, which is edited with Option Editor anyways.
424  aGrid->AutoSizeColumn( COL_NICKNAME, false );
425  aGrid->AutoSizeColumn( COL_TYPE, false );
426  aGrid->AutoSizeColumn( COL_URI, false );
427  aGrid->AutoSizeColumn( COL_DESCR, false );
428 
429  // would set this to width of title, if it was easily known.
430  aGrid->SetColSize( COL_OPTIONS, 80 );
431 
432  // Gives a selection to each grid, mainly for delete button. wxGrid's wake up with
433  // a currentCell which is sometimes not highlighted.
434  if( aGrid->GetNumberRows() > 0 )
435  aGrid->SelectRow( 0 );
436  };
437 
438  setupGrid( m_global_grid );
439 
441 
442  if( aProject )
443  {
444  m_PrjTableFilename->SetLabel( aProjectTblPath );
445  m_project_grid->SetTable( new FP_LIB_TABLE_GRID( *aProject ), true );
446  setupGrid( m_project_grid );
447  }
448  else
449  {
450  m_pageNdx = 0;
451  m_auinotebook->DeletePage( 1 );
452  m_project_grid = nullptr;
453  }
454 
455  m_path_subs_grid->SetColLabelValue( 0, _( "Name" ) );
456  m_path_subs_grid->SetColLabelValue( 1, _( "Value" ) );
457 
458  // select the last selected page
459  m_auinotebook->SetSelection( m_pageNdx );
461 
462  // for ALT+A handling, we want the initial focus to be on the first selected grid.
464 
465  // Configure button logos
466  m_append_button->SetBitmap( KiBitmap( small_plus_xpm ) );
467  m_delete_button->SetBitmap( KiBitmap( trash_xpm ) );
468  m_move_up_button->SetBitmap( KiBitmap( small_up_xpm ) );
469  m_move_down_button->SetBitmap( KiBitmap( small_down_xpm ) );
471 
472  // For aesthetic reasons, we must set the size of m_browseButton to match
473  // the other bitmaps manually (for instance m_append_button)
474  Layout(); // Needed at least on MSW to compute the actual buttons sizes,
475  // after initializing their bitmaps
476  wxSize buttonSize = m_append_button->GetSize();
477 
479  m_browseButton->SetMinSize( buttonSize );
480 
481  // Populate the browse library options
482  wxMenu* browseMenu = m_browseButton->GetSplitButtonMenu();
483 
484  for( auto& fileType : fileTypes() )
485  {
486  browseMenu->Append( fileType.first, fileType.second.m_Description );
487 
488  browseMenu->Bind( wxEVT_COMMAND_MENU_SELECTED, &PANEL_FP_LIB_TABLE::browseLibrariesHandler,
489  this, fileType.first );
490  }
491 
492  Layout();
493 
494  // This is the button only press for the browse button instead of the menu
495  m_browseButton->Bind( wxEVT_BUTTON, &PANEL_FP_LIB_TABLE::browseLibrariesHandler, this );
496 }
497 
498 
500 {
501  // When the dialog is closed it will hide the current notebook page first, which will
502  // in turn select the other one. We then end up saving its index as the "current page".
503  // So flip them back again:
504  m_pageNdx = m_pageNdx == 1 ? 0 : 1;
505 
506  // Delete the GRID_TRICKS.
507  // Any additional event handlers should be popped before the window is deleted.
508  m_global_grid->PopEventHandler( true );
509 
510  if( m_project_grid )
511  m_project_grid->PopEventHandler( true );
512 
513  m_path_subs_grid->PopEventHandler( true );
514 }
515 
516 
518 {
519  for( FP_LIB_TABLE_GRID* model : { global_model(), project_model() } )
520  {
521  if( !model )
522  continue;
523 
524  for( int r = 0; r < model->GetNumberRows(); )
525  {
526  wxString nick = model->GetValue( r, COL_NICKNAME ).Trim( false ).Trim();
527  wxString uri = model->GetValue( r, COL_URI ).Trim( false ).Trim();
528  unsigned illegalCh = 0;
529 
530  if( !nick || !uri )
531  {
532  // Delete the "empty" row, where empty means missing nick or uri.
533  // This also updates the UI which could be slow, but there should only be a few
534  // rows to delete, unless the user fell asleep on the Add Row
535  // button.
536  model->DeleteRows( r, 1 );
537  }
538  else if( ( illegalCh = LIB_ID::FindIllegalLibNicknameChar( nick, LIB_ID::ID_PCB ) ) )
539  {
540  wxString msg = wxString::Format( _( "Illegal character '%c' in Nickname: \"%s\"" ),
541  illegalCh,
542  nick );
543 
544  // show the tabbed panel holding the grid we have flunked:
545  if( model != cur_model() )
546  m_auinotebook->SetSelection( model == global_model() ? 0 : 1 );
547 
548  m_cur_grid->MakeCellVisible( r, 0 );
549  m_cur_grid->SetGridCursor( r, 1 );
550 
551  wxMessageDialog errdlg( this, msg, _( "No Colon in Nicknames" ) );
552  errdlg.ShowModal();
553  return false;
554  }
555  else
556  {
557  // set the trimmed values back into the table so they get saved to disk.
558  model->SetValue( r, COL_NICKNAME, nick );
559  model->SetValue( r, COL_URI, uri );
560  ++r; // this row was OK.
561  }
562  }
563  }
564 
565  // check for duplicate nickNames, separately in each table.
566  for( FP_LIB_TABLE_GRID* model : { global_model(), project_model() } )
567  {
568  if( !model )
569  continue;
570 
571  for( int r1 = 0; r1 < model->GetNumberRows() - 1; ++r1 )
572  {
573  wxString nick1 = model->GetValue( r1, COL_NICKNAME );
574 
575  for( int r2 = r1 + 1; r2 < model->GetNumberRows(); ++r2 )
576  {
577  wxString nick2 = model->GetValue( r2, COL_NICKNAME );
578 
579  if( nick1 == nick2 )
580  {
581  wxString msg = wxString::Format( _( "Duplicate Nicknames \"%s\"." ), nick1 );
582 
583  // show the tabbed panel holding the grid we have flunked:
584  if( model != cur_model() )
585  m_auinotebook->SetSelection( model == global_model() ? 0 : 1 );
586 
587  // go to the lower of the two rows, it is technically the duplicate:
588  m_cur_grid->MakeCellVisible( r2, 0 );
589  m_cur_grid->SetGridCursor( r2, 1 );
590 
591  wxMessageDialog errdlg( this, msg, _( "Please Delete or Modify One" ) );
592  errdlg.ShowModal();
593  return false;
594  }
595  }
596  }
597  }
598 
599  return true;
600 }
601 
602 
603 //-----<event handlers>----------------------------------
604 
605 void PANEL_FP_LIB_TABLE::pageChangedHandler( wxAuiNotebookEvent& event )
606 {
607  m_pageNdx = (unsigned) std::max( 0, m_auinotebook->GetSelection() );
609 }
610 
611 
612 void PANEL_FP_LIB_TABLE::appendRowHandler( wxCommandEvent& event )
613 {
615  return;
616 
617  if( m_cur_grid->AppendRows( 1 ) )
618  {
619  int last_row = m_cur_grid->GetNumberRows() - 1;
620 
621  // wx documentation is wrong, SetGridCursor does not make visible.
622  m_cur_grid->MakeCellVisible( last_row, 0 );
623  m_cur_grid->SetGridCursor( last_row, 1 );
624  m_cur_grid->EnableCellEditControl( true );
625  m_cur_grid->ShowCellEditControl();
626  }
627 }
628 
629 
630 void PANEL_FP_LIB_TABLE::deleteRowHandler( wxCommandEvent& event )
631 {
633  return;
634 
635  int curRow = m_cur_grid->GetGridCursorRow();
636  int curCol = m_cur_grid->GetGridCursorCol();
637 
638  // In a wxGrid, collect rows that have a selected cell, or are selected
639  // It is not so easy: it depends on the way the selection was made.
640  // Here, we collect rows selected by clicking on a row label, and rows that contain any
641  // previously-selected cells.
642  // If no candidate, just delete the row with the grid cursor.
643  wxArrayInt selectedRows = m_cur_grid->GetSelectedRows();
644  wxGridCellCoordsArray cells = m_cur_grid->GetSelectedCells();
645  wxGridCellCoordsArray blockTopLeft = m_cur_grid->GetSelectionBlockTopLeft();
646  wxGridCellCoordsArray blockBotRight = m_cur_grid->GetSelectionBlockBottomRight();
647 
648  // Add all row having cell selected to list:
649  for( unsigned ii = 0; ii < cells.GetCount(); ii++ )
650  selectedRows.Add( cells[ii].GetRow() );
651 
652  // Handle block selection
653  if( !blockTopLeft.IsEmpty() && !blockBotRight.IsEmpty() )
654  {
655  for( int i = blockTopLeft[0].GetRow(); i <= blockBotRight[0].GetRow(); ++i )
656  selectedRows.Add( i );
657  }
658 
659  // Use the row having the grid cursor only if we have no candidate:
660  if( selectedRows.size() == 0 && m_cur_grid->GetGridCursorRow() >= 0 )
661  selectedRows.Add( m_cur_grid->GetGridCursorRow() );
662 
663  if( selectedRows.size() == 0 )
664  {
665  wxBell();
666  return;
667  }
668 
669  std::sort( selectedRows.begin(), selectedRows.end() );
670 
671  // Remove selected rows (note: a row can be stored more than once in list)
672  int last_row = -1;
673 
674  for( int ii = selectedRows.GetCount()-1; ii >= 0; ii-- )
675  {
676  int row = selectedRows[ii];
677 
678  if( row != last_row )
679  {
680  last_row = row;
681  m_cur_grid->DeleteRows( row, 1 );
682  }
683  }
684 
685  if( m_cur_grid->GetNumberRows() > 0 && curRow >= 0 )
686  m_cur_grid->SetGridCursor( std::min( curRow, m_cur_grid->GetNumberRows() - 1 ), curCol );
687 }
688 
689 
690 void PANEL_FP_LIB_TABLE::moveUpHandler( wxCommandEvent& event )
691 {
693  return;
694 
695  FP_LIB_TABLE_GRID* tbl = cur_model();
696  int curRow = m_cur_grid->GetGridCursorRow();
697 
698  // @todo: add multiple selection moves.
699  if( curRow >= 1 )
700  {
701  boost::ptr_vector< LIB_TABLE_ROW >::auto_type move_me =
702  tbl->rows.release( tbl->rows.begin() + curRow );
703 
704  --curRow;
705  tbl->rows.insert( tbl->rows.begin() + curRow, move_me.release() );
706 
707  if( tbl->GetView() )
708  {
709  // Update the wxGrid
710  wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, curRow, 0 );
711  tbl->GetView()->ProcessTableMessage( msg );
712  }
713 
714  m_cur_grid->MakeCellVisible( curRow, m_cur_grid->GetGridCursorCol() );
715  m_cur_grid->SetGridCursor( curRow, m_cur_grid->GetGridCursorCol() );
716  }
717 }
718 
719 
720 void PANEL_FP_LIB_TABLE::moveDownHandler( wxCommandEvent& event )
721 {
723  return;
724 
725  FP_LIB_TABLE_GRID* tbl = cur_model();
726  int curRow = m_cur_grid->GetGridCursorRow();
727 
728  // @todo: add multiple selection moves.
729  if( unsigned( curRow + 1 ) < tbl->rows.size() )
730  {
731  boost::ptr_vector< LIB_TABLE_ROW >::auto_type move_me =
732  tbl->rows.release( tbl->rows.begin() + curRow );
733 
734  ++curRow;
735  tbl->rows.insert( tbl->rows.begin() + curRow, move_me.release() );
736 
737  if( tbl->GetView() )
738  {
739  // Update the wxGrid
740  wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, curRow - 1, 0 );
741  tbl->GetView()->ProcessTableMessage( msg );
742  }
743 
744  m_cur_grid->MakeCellVisible( curRow, m_cur_grid->GetGridCursorCol() );
745  m_cur_grid->SetGridCursor( curRow, m_cur_grid->GetGridCursorCol() );
746  }
747 }
748 
749 
750 void PANEL_FP_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event )
751 {
753  return;
754 
755  std::map<int, supportedFileType>::const_iterator fileTypeIt;
756 
757  // We are bound both to the menu and button with this one handler
758  // So we must set the file type based on it
759  if( event.GetEventType() == wxEVT_BUTTON )
760  {
761  // Let's default to adding a kicad module for just the module
762  fileTypeIt = fileTypes().find( ID_PANEL_FPLIB_ADD_KICADMOD );
763  }
764  else
765  {
766  fileTypeIt = fileTypes().find( event.GetId() );
767  }
768 
769  if( fileTypeIt == fileTypes().end() )
770  {
771  wxLogWarning( "File type selection event received but could not find the file type in the table" );
772  return;
773  }
774 
775  supportedFileType fileType = fileTypeIt->second;
776 
777  PCBNEW_SETTINGS* cfg = Pgm().GetSettingsManager().GetAppSettings<PCBNEW_SETTINGS>();
778 
779  wxArrayString files;
780 
781  wxString title;
782 
783  title.Printf( _( "Select %s Library" ), fileType.m_Description );
784 
785  if( fileType.m_IsFile )
786  {
787  wxFileDialog dlg( this, title, cfg->m_lastFootprintLibDir, wxEmptyString,
788  fileType.m_FileFilter, wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE );
789 
790  int result = dlg.ShowModal();
791 
792  if( result == wxID_CANCEL )
793  return;
794 
795  dlg.GetPaths( files );
796 
797  cfg->m_lastFootprintLibDir = dlg.GetDirectory();
798  }
799  else
800  {
801  wxDirDialog dlg( nullptr, title, cfg->m_lastFootprintLibDir,
802  wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST );
803 
804  int result = dlg.ShowModal();
805 
806  if( result == wxID_CANCEL )
807  return;
808 
809  // is there a file extension configured to hunt out their containing folders?
810  if( fileType.m_FolderSearchExtension != "" )
811  {
812  wxDir rootDir( dlg.GetPath() );
813 
814  LIBRARY_TRAVERSER traverser( fileType.m_FolderSearchExtension, rootDir.GetName() );
815  rootDir.Traverse( traverser );
816 
817  traverser.GetPaths( files );
818 
819  if( traverser.HasDirectoryOpenFailures() )
820  {
821  wxArrayString failedDirs;
822  traverser.GetPaths( failedDirs );
823  wxString detailedMsg = _( "The following directories could not be opened: \n" );
824 
825  for( auto& path : failedDirs )
826  detailedMsg << path << "\n";
827 
828  DisplayErrorMessage( this, _( "Failed to open directories to look for libraries" ),
829  detailedMsg );
830  }
831  }
832  else
833  {
834  files.Add( dlg.GetPath() );
835  }
836 
837  cfg->m_lastFootprintLibDir = dlg.GetPath();
838  }
839 
840  // Drop the last directory if the path is a .pretty folder
842  cfg->m_lastFootprintLibDir = cfg->m_lastFootprintLibDir.BeforeLast( wxFileName::GetPathSeparator() );
843 
844  const ENV_VAR_MAP& envVars = Pgm().GetLocalEnvVariables();
845  bool addDuplicates = false;
846  bool applyToAll = false;
847  wxString warning = _( "Warning: Duplicate Nickname" );
848  wxString msg = _( "A library nicknamed \"%s\" already exists." );
849  wxString detailedMsg = _( "Please change the library nickname after adding this library." );
850 
851  for( const auto& filePath : files )
852  {
853  wxFileName fn( filePath );
854  wxString nickname = LIB_ID::FixIllegalChars( fn.GetName(), LIB_ID::ID_PCB );
855  bool doAdd = true;
856 
857  if( cur_model()->ContainsNickname( nickname ) )
858  {
859  if( !applyToAll )
860  {
861  // The cancel button adds the library to the table anyway
862  addDuplicates = ( OKOrCancelDialog( this, warning, wxString::Format( msg, nickname ),
863  detailedMsg, _( "Skip" ), _( "Add Anyway" ), &applyToAll ) == wxID_CANCEL );
864  }
865 
866  doAdd = addDuplicates;
867  }
868 
869  if( doAdd && m_cur_grid->AppendRows( 1 ) )
870  {
871  int last_row = m_cur_grid->GetNumberRows() - 1;
872 
873  m_cur_grid->SetCellValue( last_row, COL_NICKNAME, nickname );
874 
875  m_cur_grid->SetCellValue( last_row, COL_TYPE, IO_MGR::ShowType( fileType.m_Plugin ) );
876 
877  // try to use path normalized to an environmental variable or project path
878  wxString path = NormalizePath( filePath, &envVars, m_projectBasePath );
879 
880  // Do not use the project path in the global library table. This will almost
881  // assuredly be wrong for a different project.
882  if( path.IsEmpty() || ( m_pageNdx == 0 && path.Contains( "${KIPRJMOD}" ) ) )
883  path = fn.GetFullPath();
884 
885  m_cur_grid->SetCellValue( last_row, COL_URI, path );
886  }
887  }
888 
889  if( !files.IsEmpty() )
890  {
891  int new_row = m_cur_grid->GetNumberRows() - 1;
892  m_cur_grid->MakeCellVisible( new_row, m_cur_grid->GetGridCursorCol() );
893  m_cur_grid->SetGridCursor( new_row, m_cur_grid->GetGridCursorCol() );
894  }
895 }
896 
897 
899 {
900  // Account for scroll bars
901  aWidth -= ( m_path_subs_grid->GetSize().x - m_path_subs_grid->GetClientSize().x );
902 
903  m_path_subs_grid->AutoSizeColumn( 0 );
904  m_path_subs_grid->SetColSize( 1, aWidth - m_path_subs_grid->GetColSize( 0 ) );
905 }
906 
907 
908 void PANEL_FP_LIB_TABLE::onSizeGrid( wxSizeEvent& event )
909 {
910  adjustPathSubsGridColumns( event.GetSize().GetX() );
911 
912  event.Skip();
913 }
914 
915 
917 {
919  return false;
920 
921  if( verifyTables() )
922  {
923  if( *global_model() != *m_global )
924  {
926 
927  m_global->Clear();
928  m_global->rows.transfer( m_global->rows.end(), global_model()->rows.begin(),
929  global_model()->rows.end(), global_model()->rows );
930  m_global->reindex();
931  }
932 
933  if( project_model() && *project_model() != *m_project )
934  {
936 
937  m_project->Clear();
938  m_project->rows.transfer( m_project->rows.end(), project_model()->rows.begin(),
939  project_model()->rows.end(), project_model()->rows );
940  m_project->reindex();
941  }
942 
943  return true;
944  }
945 
946  return false;
947 }
948 
949 
953 {
954  wxRegEx re( ".*?(\\$\\{(.+?)\\})|(\\$\\((.+?)\\)).*?", wxRE_ADVANCED );
955  wxASSERT( re.IsValid() ); // wxRE_ADVANCED is required.
956 
957  std::set< wxString > unique;
958 
959  // clear the table
960  m_path_subs_grid->DeleteRows( 0, m_path_subs_grid->GetNumberRows() );
961 
962  for( FP_LIB_TABLE_GRID* tbl : { global_model(), project_model() } )
963  {
964  if( !tbl )
965  continue;
966 
967  for( int row = 0; row < tbl->GetNumberRows(); ++row )
968  {
969  wxString uri = tbl->GetValue( row, COL_URI );
970 
971  while( re.Matches( uri ) )
972  {
973  wxString envvar = re.GetMatch( uri, 2 );
974 
975  // if not ${...} form then must be $(...)
976  if( envvar.IsEmpty() )
977  envvar = re.GetMatch( uri, 4 );
978 
979  // ignore duplicates
980  unique.insert( envvar );
981 
982  // delete the last match and search again
983  uri.Replace( re.GetMatch( uri, 0 ), wxEmptyString );
984  }
985  }
986  }
987 
988  // Make sure this special environment variable shows up even if it was
989  // not used yet. It is automatically set by KiCad to the directory holding
990  // the current project.
991  unique.insert( PROJECT_VAR_NAME );
992  unique.insert( FP_LIB_TABLE::GlobalPathEnvVariableName() );
993  // This special environment variable is used to locate 3d shapes
994  unique.insert( KISYS3DMOD );
995 
996  for( const wxString& evName : unique )
997  {
998  int row = m_path_subs_grid->GetNumberRows();
999  m_path_subs_grid->AppendRows( 1 );
1000 
1001  m_path_subs_grid->SetCellValue( row, 0, wxT( "${" ) + evName + wxT( "}" ) );
1002  m_path_subs_grid->SetCellEditor( row, 0, new GRID_CELL_READONLY_TEXT_EDITOR() );
1003 
1004  wxString evValue;
1005  wxGetEnv( evName, &evValue );
1006  m_path_subs_grid->SetCellValue( row, 1, evValue );
1007  m_path_subs_grid->SetCellEditor( row, 1, new GRID_CELL_READONLY_TEXT_EDITOR() );
1008  }
1009 
1010  // No combobox editors here, but it looks better if its consistent with the other
1011  // grids in the dialog.
1012  m_path_subs_grid->SetDefaultRowSize( m_path_subs_grid->GetDefaultRowSize() + 2 );
1013 
1014  adjustPathSubsGridColumns( m_path_subs_grid->GetRect().GetWidth() );
1015 }
1016 
1017 //-----</event handlers>---------------------------------
1018 
1019 
1020 
1022 
1023 
1024 void InvokePcbLibTableEditor( KIWAY* aKiway, wxWindow* aCaller )
1025 {
1026  FP_LIB_TABLE* globalTable = &GFootprintTable;
1027  wxString globalTablePath = FP_LIB_TABLE::GetGlobalTableFileName();
1028  FP_LIB_TABLE* projectTable = aKiway->Prj().PcbFootprintLibs();
1029  wxString projectTablePath = aKiway->Prj().FootprintLibTblName();
1030  wxString msg;
1031 
1032  DIALOG_EDIT_LIBRARY_TABLES dlg( aCaller, _( "Footprint Libraries" ) );
1033  dlg.SetKiway( &dlg, aKiway );
1034 
1035  if( aKiway->Prj().IsNullProject() )
1036  projectTable = nullptr;
1037 
1038  dlg.InstallPanel( new PANEL_FP_LIB_TABLE( &dlg, globalTable, globalTablePath,
1039  projectTable, projectTablePath,
1040  aKiway->Prj().GetProjectPath() ) );
1041 
1042  if( dlg.ShowModal() == wxID_CANCEL )
1043  return;
1044 
1045  if( dlg.m_GlobalTableChanged )
1046  {
1047  try
1048  {
1049  globalTable->Save( globalTablePath );
1050  }
1051  catch( const IO_ERROR& ioe )
1052  {
1053  msg.Printf( _( "Error saving global library table:\n\n%s" ), ioe.What() );
1054  wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
1055  }
1056  }
1057 
1058  if( projectTable && dlg.m_ProjectTableChanged )
1059  {
1060  try
1061  {
1062  projectTable->Save( projectTablePath );
1063  }
1064  catch( const IO_ERROR& ioe )
1065  {
1066  msg.Printf( _( "Error saving project-specific library table:\n\n%s" ), ioe.What() );
1067  wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
1068  }
1069  }
1070 
1071  auto editor = (FOOTPRINT_EDIT_FRAME*) aKiway->Player( FRAME_FOOTPRINT_EDITOR, false );
1072 
1073  if( editor )
1074  editor->SyncLibraryTree( true );
1075 
1076  auto viewer = (FOOTPRINT_VIEWER_FRAME*) aKiway->Player( FRAME_FOOTPRINT_VIEWER, false );
1077 
1078  if( viewer )
1079  viewer->ReCreateLibraryList();
1080 }
void DisplayError(wxWindow *aParent, const wxString &aText, int aDisplayTime)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:240
FP_GRID_TRICKS(DIALOG_EDIT_LIBRARY_TABLES *aParent, WX_GRID *aGrid)
const BITMAP_OPAQUE trash_xpm[1]
Definition: trash.cpp:34
wxString m_FileFilter
Filter used for file pickers if m_IsFile is true.
const BITMAP_OPAQUE folder_xpm[1]
Definition: folder.cpp:20
void SetKiway(wxWindow *aDest, KIWAY *aKiway)
Function SetKiway.
bool m_IsFile
Whether the library is a folder or a file.
void push_back(LIB_TABLE_ROW *aRow) override
const wxString & GetOptions() const
Return the options string, which may hold a password or anything else needed to instantiate the under...
void moveDownHandler(wxCommandEvent &event) override
wxString EagleFootprintLibPathWildcard()
void InvokePluginOptionsEditor(wxWindow *aCaller, const wxString &aNickname, const wxString &aPluginType, const wxString &aOptions, wxString *aResult)
Function InvokePluginOptionsEditor calls DIALOG_FP_PLUGIN_OPTIONS dialog so that plugin options set c...
void SetBitmap(const wxBitmap &aBmp)
Hold a record identifying a library accessed by the appropriate plug in object in the LIB_TABLE.
static const std::map< int, supportedFileType > & fileTypes()
Map with event id as the key to supported file types that will be listed for the add a library option...
void Clear()
Delete all rows.
void GetFailedPaths(wxArrayString &aPathArray)
void DisplayErrorMessage(wxWindow *aParent, const wxString &aText, const wxString &aExtraInfo)
Display an error message with aMessage.
Definition: confirm.cpp:253
FP_LIB_TABLE_ROW.
Definition: fp_lib_table.h:42
wxMenu * GetSplitButtonMenu()
This file is part of the common library.
void showPopupMenu(wxMenu &menu) override
void reindex()
const std::string KiCadFootprintFileExtension
FP_LIB_TABLE * m_project
bool ContainsNickname(const wxString &aNickname)
VTBL_ENTRY PROJECT & Prj() const
Function Prj returns the PROJECT associated with this KIWAY.
Definition: kiway.cpp:173
FP_LIB_TABLE GFootprintTable
!!!!!!!!!!!!!! This code is obsolete because of the merge into pcbnew, don't bother with it.
unsigned GetCount() const
Get the number of rows contained in the table.
virtual wxDirTraverseResult OnFile(const wxString &aFileName) override
#define PROJECT_VAR_NAME
A variable name whose value holds the current project directory.
Definition: project.h:38
Component library viewer main window.
void appendRowHandler(wxCommandEvent &event) override
void SetMinSize(const wxSize &aSize) override
GRID_TRICKS is used to add mouse and command handling (such as cut, copy, and paste) to a WX_GRID ins...
Definition: grid_tricks.h:52
void adjustPathSubsGridColumns(int aWidth)
static unsigned FindIllegalLibNicknameChar(const UTF8 &aNickname, LIB_ID_TYPE aType)
Looks for characters that are illegal in library nicknames.
Definition: lib_id.cpp:404
#define MYID_OPTIONS_EDITOR
#define KISYS3DMOD
A variable name whose value holds the path of 3D shape files.
Definition: eda_3d_viewer.h:45
LIB_TABLE_ROWS rows
Container that describes file type info for the add a library options.
void SetTable(wxGridTableBase *table, bool aTakeOwnership=false)
Hide wxGrid's SetTable() method with one which doesn't mess up the grid column widths when setting th...
Definition: wx_grid.cpp:65
static const wxString ShowType(PCB_FILE_T aFileType)
Function ShowType returns a brief name for a plugin, given aFileType enum.
Definition: io_mgr.cpp:81
virtual const wxString GetType() const =0
Return the type of library represented by this row.
int GetNumberRows() override
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:102
wxString LegacyFootprintLibPathWildcard()
VTBL_ENTRY const wxString GetProjectPath() const
Function GetProjectPath returns the full path of the project.
Definition: project.cpp:121
void SetInitialFocus(wxWindow *aWindow)
Sets the window (usually a wxTextCtrl) that should be focused when the dialog is shown.
Definition: dialog_shim.h:115
Geda PCB file formats.
Definition: io_mgr.h:64
size_t size() const override
This abstract base class mixes any object derived from LIB_TABLE into wxGridTableBase so the result c...
LIB_TABLE_ROWS_ITER insert(LIB_TABLE_ROWS_ITER aIterator, LIB_TABLE_ROW *aRow) override
void InvokePcbLibTableEditor(KIWAY *aKiway, wxWindow *aCaller)
Function InvokePcbLibTableEditor shows the modal DIALOG_FP_LIB_TABLE for purposes of editing the glob...
WX_GRID * m_grid
I don't own the grid, but he owns me.
Definition: grid_tricks.h:59
virtual void paste_text(const wxString &cb_text)
#define TO_UTF8(wxstring)
Macro TO_UTF8 converts a wxString to a UTF8 encoded C string for all wxWidgets build modes.
Definition: macros.h:95
static size_t m_pageNdx
void moveUpHandler(wxCommandEvent &event) override
wxBitmap KiBitmap(BITMAP_DEF aBitmap)
Construct a wxBitmap from a memory record, held in a BITMAP_DEF.
Definition: bitmap.cpp:82
const wxString & GetNickName() const
static const wxString GlobalPathEnvVariableName()
Function GlobalPathEnvVarVariableName.
LIB_TABLE_ROW * clone() const
virtual void Parse(LIB_TABLE_LEXER *aLexer) override
Parse the #LIB_TABLE_LEXER s-expression library table format into the appropriate LIB_TABLE_ROW objec...
PANEL_FP_LIB_TABLE(DIALOG_EDIT_LIBRARY_TABLES *aParent, FP_LIB_TABLE *aGlobal, const wxString &aGlobalTblPath, FP_LIB_TABLE *aProject, const wxString &aProjectTblPath, const wxString &aProjectBasePath)
VTBL_ENTRY KIWAY_PLAYER * Player(FRAME_T aFrameType, bool doCreate=true, wxTopLevelWindow *aParent=NULL)
Function Player returns the KIWAY_PLAYER* given a FRAME_T.
Definition: kiway.cpp:343
virtual const wxString What() const
A composite of Problem() and Where()
Definition: exceptions.cpp:29
std::map< wxString, ENV_VAR_ITEM > ENV_VAR_MAP
Definition: pgm_base.h:117
wxString m_FolderSearchExtension
In case of folders it stands for extensions of files stored inside.
FP_LIB_TABLE_GRID * global_model() const
DIALOG_EDIT_LIBRARY_TABLES * m_dialog
std::unordered_map< wxString, int > m_foundDirs
FP_LIB_TABLE_GRID * project_model() const
FP_LIB_TABLE_GRID * cur_model() const
const BITMAP_OPAQUE small_down_xpm[1]
Definition: small_down.cpp:24
void paste_text(const wxString &cb_text) override
handle specialized clipboard text, with leading "(fp_lib_table", OR spreadsheet formatted text.
Definition of file extensions used in Kicad.
KIWAY is a minimalistic software bus for communications between various DLLs/DSOs (DSOs) within the s...
Definition: kiway.h:273
void deleteRowHandler(wxCommandEvent &event) override
Editor for wxGrid cells that adds a file/folder browser to the grid input field.
bool CommitPendingChanges(bool aQuietMode=false)
Close any open cell edit controls.
Definition: wx_grid.cpp:180
bool AppendRows(size_t aNumRows=1) override
void optionsEditor(int aRow)
FormatType fileType(const char *aFileName)
Definition: loadmodel.cpp:271
int OKOrCancelDialog(wxWindow *aParent, const wxString &aWarning, const wxString &aMessage, const wxString &aDetailedMessage, const wxString &aOKLabel, const wxString &aCancelLabel, bool *aApplyToAll)
Displays a warning dialog with aMessage and returns the user response.
Definition: confirm.cpp:215
const BITMAP_OPAQUE small_up_xpm[1]
Definition: small_up.cpp:25
VTBL_ENTRY bool IsNullProject() const
Checks if this project is a null project (i.e.
Definition: project.cpp:133
void browseLibrariesHandler(wxCommandEvent &event)
bool verifyTables()
Trim important fields, removes blank row entries, and checks for duplicates.
void SetWidthPadding(int aPadding)
Class PANEL_FP_LIB_TABLE_BASE.
LIB_TABLE_ROWS_ITER begin() override
void pageChangedHandler(wxAuiNotebookEvent &event) override
VTBL_ENTRY FP_LIB_TABLE * PcbFootprintLibs(KIWAY &aKiway)
Return the table of footprint libraries.
Definition: project.cpp:283
bool TransferDataFromWindow() override
virtual wxDirTraverseResult OnDir(const wxString &aDirName) override
LIBRARY_TRAVERSER(wxString aSearchExtension, wxString aInitialDir)
Struct PARSE_ERROR contains a filename or source description, a problem input line,...
Definition: ki_exception.h:123
Legacy Pcbnew file formats prior to s-expression.
Definition: io_mgr.h:56
see class PGM_BASE
virtual void doPopupSelection(wxCommandEvent &event)
Declaration of the eda_3d_viewer class.
LIB_TABLE_ROW & At(unsigned aIndex)
Get the 'n'th LIB_TABLE_ROW object.
void Format(OUTPUTFORMATTER *out, int aNestLevel, int aCtl, CPTREE &aTree)
Function Format outputs a PTREE into s-expression format via an OUTPUTFORMATTER derivative.
Definition: ptree.cpp:201
LIB_TABLE_ROW * makeNewRow() override
virtual void showPopupMenu(wxMenu &menu)
FP_LIB_TABLE_GRID(const FP_LIB_TABLE &aTableToEdit)
#define _(s)
Definition: 3d_actions.cpp:33
virtual wxDirTraverseResult OnOpenError(const wxString &aOpenErrorName) override
void GetPaths(wxArrayString &aPathArray)
LIB_TABLE_ROWS::iterator LIB_TABLE_ROWS_ITER
void Save(const wxString &aFileName) const
Write this library table to aFileName in s-expression form.
Traverser implementation that looks to find any and all "folder" libraries by looking for files with ...
LIB_TABLE_ROWS_ITER erase(LIB_TABLE_ROWS_ITER aFirst, LIB_TABLE_ROWS_ITER aLast) override
This class builds a wxGridTableBase by wrapping an FP_LIB_TABLE object.
const std::string GedaPcbFootprintLibFileExtension
DIALOG_EDIT_LIBRARY_TABLES * m_parent
static UTF8 FixIllegalChars(const UTF8 &aLibItemName, LIB_ID_TYPE aType, bool aLib=false)
Replace illegal LIB_ID item name characters with underscores '_'.
Definition: lib_id.cpp:352
void SetOptions(const wxString &aOptions)
Change the library options strings.
void onSizeGrid(wxSizeEvent &event) override
FP_LIB_TABLE * m_global
Dialog to show and edit symbol library tables.
IO_MGR::PCB_FILE_T m_Plugin
wxString m_lastFootprintLibDir
std::unordered_map< wxString, int > m_failedDirs
PCB_FILE_T
Enum PCB_FILE_T is a set of file types that the IO_MGR knows about, and for which there has been a pl...
Definition: io_mgr.h:54
STRING_LINE_READER is a LINE_READER that reads from a multiline 8 bit wide std::string.
Definition: richio.h:255
bool handleDoubleClick(wxGridEvent &aEvent) override
wxString m_Description
Description shown in the file picker dialog.
Struct IO_ERROR is a class used to hold an error message and may be used when throwing exceptions con...
Definition: ki_exception.h:76
const std::string KiCadFootprintLibPathExtension
const BITMAP_OPAQUE small_plus_xpm[1]
Definition: small_plus.cpp:20
void doPopupSelection(wxCommandEvent &event) override
LIB_TABLE_ROW * at(size_t aIndex) override
VTBL_ENTRY const wxString FootprintLibTblName() const
Function FootprintLibTblName returns the path and filename of this project's fp-lib-table,...
Definition: project.cpp:145
wxString NormalizePath(const wxFileName &aFilePath, const ENV_VAR_MAP *aEnvVars, const wxString &aProjectPath)
Normalizes a file path to an environmental variable, if possible.
Definition: env_paths.cpp:68
static wxString GetGlobalTableFileName()
Function GetGlobalTableFileName.
void populateEnvironReadOnlyTable()
Populate the readonly environment variable table with names and values by examining all the full_uri ...
S-expression Pcbnew file format.
Definition: io_mgr.h:57