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-2019 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 <fctsys.h>
39 #include <project.h>
40 #include <3d_viewer/eda_3d_viewer.h> // for KISYS3DMOD
41 #include <panel_fp_lib_table.h>
42 #include <lib_id.h>
43 #include <fp_lib_table.h>
44 #include <lib_table_lexer.h>
45 #include <invoke_pcb_dialog.h>
46 #include <bitmaps.h>
47 #include <grid_tricks.h>
48 #include <widgets/wx_grid.h>
49 #include <confirm.h>
50 #include <lib_table_grid.h>
52 #include <pgm_base.h>
53 #include <pcb_edit_frame.h>
54 #include <env_paths.h>
56 #include <footprint_viewer_frame.h>
57 #include <footprint_edit_frame.h>
58 #include <kiway.h>
61 #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 
91 static const std::map<int, supportedFileType>& fileTypes()
92 {
93  /*
94  * TODO(C++20): Clean this up
95  * This is wrapped inside a function to prevent a static initialization order fiasco with the file extension
96  * variables. Once C++20 is allowed in KiCad code, those file extensions can be made constexpr and this can
97  * be removed from a function call and placed in the file normally.
98  */
99  static const std::map<int, supportedFileType> fileTypes =
100  {
101  { ID_PANEL_FPLIB_ADD_KICADMOD, { "KiCad (folder with .kicad_mod files)", "", KiCadFootprintFileExtension, false, IO_MGR::KICAD_SEXP } },
102  { ID_PANEL_FPLIB_ADD_EAGLE6, { "Eagle 6.x (*.lbr)", EagleFootprintLibPathWildcard(), "", true, IO_MGR::EAGLE } },
103  { ID_PANEL_FPLIB_ADD_KICADLEGACY, { "KiCad legacy (*.mod)", LegacyFootprintLibPathWildcard(), "", true, IO_MGR::LEGACY } },
104  { ID_PANEL_FPLIB_ADD_GEDA, { "Geda (folder with *.fp files)", "", GedaPcbFootprintLibFileExtension, false, IO_MGR::GEDA_PCB } },
105  };
106 
107  return fileTypes;
108 }
109 // clang-format on
110 
111 
116 class LIBRARY_TRAVERSER : public wxDirTraverser
117 {
118 public:
119  LIBRARY_TRAVERSER( wxString aSearchExtension, wxString aInitialDir )
120  : m_searchExtension( aSearchExtension ),
121  m_currentDir( aInitialDir )
122  {
123  }
124 
125 
126  virtual wxDirTraverseResult OnFile( const wxString& aFileName ) override
127  {
128  wxFileName file( aFileName );
129  if( m_searchExtension.IsSameAs( file.GetExt(), false ) )
130  {
131  m_foundDirs.insert( { m_currentDir, 1 } );
132  }
133 
134  return wxDIR_CONTINUE;
135  }
136 
137 
138  virtual wxDirTraverseResult OnOpenError( const wxString& aOpenErrorName ) override
139  {
140  m_failedDirs.insert( { aOpenErrorName, 1 } );
141  return wxDIR_IGNORE;
142  }
143 
144 
146  {
147  return m_failedDirs.size() > 0;
148  }
149 
150 
151  virtual wxDirTraverseResult OnDir( const wxString& aDirName ) override
152  {
153  m_currentDir = aDirName;
154  return wxDIR_CONTINUE;
155  }
156 
157 
158  void GetPaths( wxArrayString& aPathArray )
159  {
160  for( auto foundDirsPair : m_foundDirs )
161  {
162  aPathArray.Add( foundDirsPair.first );
163  }
164  }
165 
166 
167  void GetFailedPaths( wxArrayString& aPathArray )
168  {
169  for( auto failedDirsPair : m_failedDirs )
170  {
171  aPathArray.Add( failedDirsPair.first );
172  }
173  }
174 
175 private:
177  wxString m_currentDir;
178  std::unordered_map<wxString, int> m_foundDirs;
179  std::unordered_map<wxString, int> m_failedDirs;
180 };
181 
182 
187 {
188  friend class PANEL_FP_LIB_TABLE;
189  friend class FP_GRID_TRICKS;
190 
191 protected:
192  LIB_TABLE_ROW* at( size_t aIndex ) override { return &rows.at( aIndex ); }
193 
194  size_t size() const override { return rows.size(); }
195 
197  {
198  return dynamic_cast< LIB_TABLE_ROW* >( new FP_LIB_TABLE_ROW );
199  }
200 
201  LIB_TABLE_ROWS_ITER begin() override { return rows.begin(); }
202 
204  {
205  return rows.insert( aIterator, aRow );
206  }
207 
208  void push_back( LIB_TABLE_ROW* aRow ) override { rows.push_back( aRow ); }
209 
211  {
212  return rows.erase( aFirst, aLast );
213  }
214 
215 public:
216 
217  FP_LIB_TABLE_GRID( const FP_LIB_TABLE& aTableToEdit )
218  {
219  rows = aTableToEdit.rows;
220  }
221 };
222 
223 
224 #define MYID_OPTIONS_EDITOR 15151
225 
226 
228 {
229 public:
231  GRID_TRICKS( aGrid ),
232  m_dialog( aParent )
233  { }
234 
235 protected:
237 
238  void optionsEditor( int aRow )
239  {
240  FP_LIB_TABLE_GRID* tbl = (FP_LIB_TABLE_GRID*) m_grid->GetTable();
241 
242  if( tbl->GetNumberRows() > aRow )
243  {
244  LIB_TABLE_ROW* row = tbl->at( (size_t) aRow );
245  const wxString& options = row->GetOptions();
246  wxString result = options;
247 
248  InvokePluginOptionsEditor( m_dialog, row->GetNickName(), row->GetType(), options,
249  &result );
250 
251  if( options != result )
252  {
253  row->SetOptions( result );
254  m_grid->Refresh();
255  }
256  }
257  }
258 
259  bool handleDoubleClick( wxGridEvent& aEvent ) override
260  {
261  if( aEvent.GetCol() == COL_OPTIONS )
262  {
263  optionsEditor( aEvent.GetRow() );
264  return true;
265  }
266 
267  return false;
268  }
269 
270  void showPopupMenu( wxMenu& menu ) override
271  {
272  if( m_grid->GetGridCursorCol() == COL_OPTIONS )
273  {
274  menu.Append( MYID_OPTIONS_EDITOR, _( "Options Editor..." ), _( "Edit options" ) );
275  menu.AppendSeparator();
276  }
277 
279  }
280 
281  void doPopupSelection( wxCommandEvent& event ) override
282  {
283  if( event.GetId() == MYID_OPTIONS_EDITOR )
284  optionsEditor( m_grid->GetGridCursorRow() );
285  else
287  }
288 
291  void paste_text( const wxString& cb_text ) override
292  {
293  FP_LIB_TABLE_GRID* tbl = (FP_LIB_TABLE_GRID*) m_grid->GetTable();
294  size_t ndx = cb_text.find( "(fp_lib_table" );
295 
296  if( ndx != std::string::npos )
297  {
298  // paste the FP_LIB_TABLE_ROWs of s-expression (fp_lib_table), starting
299  // at column 0 regardless of current cursor column.
300 
301  STRING_LINE_READER slr( TO_UTF8( cb_text ), "Clipboard" );
302  LIB_TABLE_LEXER lexer( &slr );
303  FP_LIB_TABLE tmp_tbl;
304  bool parsed = true;
305 
306  try
307  {
308  tmp_tbl.Parse( &lexer );
309  }
310  catch( PARSE_ERROR& pe )
311  {
312  DisplayError( m_dialog, pe.What() );
313  parsed = false;
314  }
315 
316  if( parsed )
317  {
318  // make sure the table is big enough...
319  if( tmp_tbl.GetCount() > (unsigned) tbl->GetNumberRows() )
320  tbl->AppendRows( tmp_tbl.GetCount() - tbl->GetNumberRows() );
321 
322  for( unsigned i = 0; i < tmp_tbl.GetCount(); ++i )
323  tbl->rows.replace( i, tmp_tbl.At( i ).clone() );
324  }
325 
326  m_grid->AutoSizeColumns( false );
327  }
328  else
329  {
330  // paste spreadsheet formatted text.
331  GRID_TRICKS::paste_text( cb_text );
332 
333  m_grid->AutoSizeColumns( false );
334  }
335  }
336 };
337 
338 
340  FP_LIB_TABLE* aGlobal, const wxString& aGlobalTblPath,
341  FP_LIB_TABLE* aProject, const wxString& aProjectTblPath,
342  const wxString& aProjectBasePath ) :
343  PANEL_FP_LIB_TABLE_BASE( aParent ),
344  m_global( aGlobal ),
345  m_project( aProject ),
346  m_projectBasePath( aProjectBasePath ),
347  m_parent( aParent )
348 {
349  // For user info, shows the table filenames:
350  m_GblTableFilename->SetLabel( aGlobalTblPath );
351  m_PrjTableFilename->SetLabel( aProjectTblPath );
352 
353  m_global_grid->SetTable( new FP_LIB_TABLE_GRID( *aGlobal ), true );
354  m_project_grid->SetTable( new FP_LIB_TABLE_GRID( *aProject ), true );
355 
356  // Give a bit more room for wxChoice editors
357  m_global_grid->SetDefaultRowSize( m_global_grid->GetDefaultRowSize() + 4 );
358  m_project_grid->SetDefaultRowSize( m_project_grid->GetDefaultRowSize() + 4 );
359 
360  // add Cut, Copy, and Paste to wxGrids
361  m_global_grid->PushEventHandler( new FP_GRID_TRICKS( m_parent, m_global_grid ) );
362  m_project_grid->PushEventHandler( new FP_GRID_TRICKS( m_parent, m_project_grid ) );
363  m_path_subs_grid->PushEventHandler( new GRID_TRICKS( m_path_subs_grid ) );
364 
365  m_global_grid->SetSelectionMode( wxGrid::wxGridSelectRows );
366  m_project_grid->SetSelectionMode( wxGrid::wxGridSelectRows );
367 
368  m_global_grid->AutoSizeColumns( false );
369  m_project_grid->AutoSizeColumns( false );
370 
371  wxArrayString choices;
372 
373  choices.Add( IO_MGR::ShowType( IO_MGR::KICAD_SEXP ) );
374 #if defined(BUILD_GITHUB_PLUGIN)
375  choices.Add( IO_MGR::ShowType( IO_MGR::GITHUB ) );
376 #endif
377  choices.Add( IO_MGR::ShowType( IO_MGR::LEGACY ) );
378  choices.Add( IO_MGR::ShowType( IO_MGR::EAGLE ) );
379  choices.Add( IO_MGR::ShowType( IO_MGR::GEDA_PCB ) );
380 
381  /* PCAD_PLUGIN does not support Footprint*() functions
382  choices.Add( IO_MGR::ShowType( IO_MGR::PCAD ) );
383  */
384 
386 
387  for( wxGrid* g : { m_global_grid, m_project_grid } )
388  {
389  wxGridCellAttr* attr;
390 
391  attr = new wxGridCellAttr;
392  attr->SetEditor( new GRID_CELL_PATH_EDITOR( m_parent, &m_lastBrowseDir, wxEmptyString ) );
393  g->SetColAttr( COL_URI, attr );
394 
395  attr = new wxGridCellAttr;
396  attr->SetEditor( new wxGridCellChoiceEditor( choices ) );
397  g->SetColAttr( COL_TYPE, attr );
398 
399  attr = new wxGridCellAttr;
400  attr->SetRenderer( new wxGridCellBoolRenderer() );
401  attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
402  g->SetColAttr( COL_ENABLED, attr );
403 
404  // all but COL_OPTIONS, which is edited with Option Editor anyways.
405  g->AutoSizeColumn( COL_NICKNAME, false );
406  g->AutoSizeColumn( COL_TYPE, false );
407  g->AutoSizeColumn( COL_URI, false );
408  g->AutoSizeColumn( COL_DESCR, false );
409 
410  // would set this to width of title, if it was easily known.
411  g->SetColSize( COL_OPTIONS, 80 );
412  }
413 
414  m_path_subs_grid->SetColLabelValue( 0, _( "Name" ) );
415  m_path_subs_grid->SetColLabelValue( 1, _( "Value" ) );
416 
417  // select the last selected page
418  m_auinotebook->SetSelection( m_pageNdx );
420 
421  // for ALT+A handling, we want the initial focus to be on the first selected grid.
423 
424  // Configure button logos
425  m_append_button->SetBitmap( KiBitmap( small_plus_xpm ) );
426  m_delete_button->SetBitmap( KiBitmap( trash_xpm ) );
427  m_move_up_button->SetBitmap( KiBitmap( small_up_xpm ) );
428  m_move_down_button->SetBitmap( KiBitmap( small_down_xpm ) );
429 
431  // We must set the size to match the other bitmaps manually
433  m_browseButton->SetMinSize( wxSize( 30, 30 ) );
434 
435  // Gives a selection to each grid, mainly for delete button. wxGrid's wake up with
436  // a currentCell which is sometimes not highlighted.
437  if( m_global_grid->GetNumberRows() > 0 )
438  m_global_grid->SelectRow( 0 );
439 
440  if( m_project_grid->GetNumberRows() > 0 )
441  m_project_grid->SelectRow( 0 );
442 
443  // Populate the browse library options
444  wxMenu* browseMenu = m_browseButton->GetSplitButtonMenu();
445  for( auto& fileType : fileTypes() )
446  {
447  browseMenu->Append( fileType.first, fileType.second.m_Description );
448 
449  browseMenu->Bind( wxEVT_COMMAND_MENU_SELECTED, &PANEL_FP_LIB_TABLE::browseLibrariesHandler,
450  this, fileType.first );
451  }
452 
453  // This is the button only press for the browse button instead of the menu
454  m_browseButton->Bind( wxEVT_BUTTON, &PANEL_FP_LIB_TABLE::browseLibrariesHandler, this );
455 }
456 
457 
459 {
460  // When the dialog is closed it will hide the current notebook page first, which will
461  // in turn select the other one. We then end up saving its index as the "current page".
462  // So flip them back again:
463  m_pageNdx = m_pageNdx == 1 ? 0 : 1;
464 
465  // Delete the GRID_TRICKS.
466  // Any additional event handlers should be popped before the window is deleted.
467  m_global_grid->PopEventHandler( true );
468  m_project_grid->PopEventHandler( true );
469  m_path_subs_grid->PopEventHandler( true );
470 }
471 
472 
474 {
475  for( FP_LIB_TABLE_GRID* model : { global_model(), project_model() } )
476  {
477  for( int r = 0; r < model->GetNumberRows(); )
478  {
479  wxString nick = model->GetValue( r, COL_NICKNAME ).Trim( false ).Trim();
480  wxString uri = model->GetValue( r, COL_URI ).Trim( false ).Trim();
481  unsigned illegalCh = 0;
482 
483  if( !nick || !uri )
484  {
485  // Delete the "empty" row, where empty means missing nick or uri.
486  // This also updates the UI which could be slow, but there should only be a few
487  // rows to delete, unless the user fell asleep on the Add Row
488  // button.
489  model->DeleteRows( r, 1 );
490  }
491  else if( ( illegalCh = LIB_ID::FindIllegalLibNicknameChar( nick, LIB_ID::ID_PCB ) ) )
492  {
493  wxString msg = wxString::Format( _( "Illegal character '%c' in Nickname: \"%s\"" ),
494  illegalCh,
495  nick );
496 
497  // show the tabbed panel holding the grid we have flunked:
498  if( model != cur_model() )
499  m_auinotebook->SetSelection( model == global_model() ? 0 : 1 );
500 
501  m_cur_grid->MakeCellVisible( r, 0 );
502  m_cur_grid->SetGridCursor( r, 1 );
503 
504  wxMessageDialog errdlg( this, msg, _( "No Colon in Nicknames" ) );
505  errdlg.ShowModal();
506  return false;
507  }
508  else
509  {
510  // set the trimmed values back into the table so they get saved to disk.
511  model->SetValue( r, COL_NICKNAME, nick );
512  model->SetValue( r, COL_URI, uri );
513  ++r; // this row was OK.
514  }
515  }
516  }
517 
518  // check for duplicate nickNames, separately in each table.
519  for( FP_LIB_TABLE_GRID* model : { global_model(), project_model() } )
520  {
521  for( int r1 = 0; r1 < model->GetNumberRows() - 1; ++r1 )
522  {
523  wxString nick1 = model->GetValue( r1, COL_NICKNAME );
524 
525  for( int r2 = r1 + 1; r2 < model->GetNumberRows(); ++r2 )
526  {
527  wxString nick2 = model->GetValue( r2, COL_NICKNAME );
528 
529  if( nick1 == nick2 )
530  {
531  wxString msg = wxString::Format( _( "Duplicate Nicknames \"%s\"." ), nick1 );
532 
533  // show the tabbed panel holding the grid we have flunked:
534  if( model != cur_model() )
535  m_auinotebook->SetSelection( model == global_model() ? 0 : 1 );
536 
537  // go to the lower of the two rows, it is technically the duplicate:
538  m_cur_grid->MakeCellVisible( r2, 0 );
539  m_cur_grid->SetGridCursor( r2, 1 );
540 
541  wxMessageDialog errdlg( this, msg, _( "Please Delete or Modify One" ) );
542  errdlg.ShowModal();
543  return false;
544  }
545  }
546  }
547  }
548 
549  return true;
550 }
551 
552 
553 //-----<event handlers>----------------------------------
554 
555 void PANEL_FP_LIB_TABLE::pageChangedHandler( wxAuiNotebookEvent& event )
556 {
557  m_pageNdx = (unsigned) std::max( 0, m_auinotebook->GetSelection() );
559 }
560 
561 
562 void PANEL_FP_LIB_TABLE::appendRowHandler( wxCommandEvent& event )
563 {
565  return;
566 
567  if( m_cur_grid->AppendRows( 1 ) )
568  {
569  int last_row = m_cur_grid->GetNumberRows() - 1;
570 
571  // wx documentation is wrong, SetGridCursor does not make visible.
572  m_cur_grid->MakeCellVisible( last_row, 0 );
573  m_cur_grid->SetGridCursor( last_row, 1 );
574  m_cur_grid->EnableCellEditControl( true );
575  m_cur_grid->ShowCellEditControl();
576  }
577 }
578 
579 
580 void PANEL_FP_LIB_TABLE::deleteRowHandler( wxCommandEvent& event )
581 {
583  return;
584 
585  int curRow = m_cur_grid->GetGridCursorRow();
586  int curCol = m_cur_grid->GetGridCursorCol();
587 
588  // In a wxGrid, collect rows that have a selected cell, or are selected
589  // It is not so easy: it depends on the way the selection was made.
590  // Here, we collect rows selected by clicking on a row label, and rows that contain any
591  // previously-selected cells.
592  // If no candidate, just delete the row with the grid cursor.
593  wxArrayInt selectedRows = m_cur_grid->GetSelectedRows();
594  wxGridCellCoordsArray cells = m_cur_grid->GetSelectedCells();
595  wxGridCellCoordsArray blockTopLeft = m_cur_grid->GetSelectionBlockTopLeft();
596  wxGridCellCoordsArray blockBotRight = m_cur_grid->GetSelectionBlockBottomRight();
597 
598  // Add all row having cell selected to list:
599  for( unsigned ii = 0; ii < cells.GetCount(); ii++ )
600  selectedRows.Add( cells[ii].GetRow() );
601 
602  // Handle block selection
603  if( !blockTopLeft.IsEmpty() && !blockBotRight.IsEmpty() )
604  {
605  for( int i = blockTopLeft[0].GetRow(); i <= blockBotRight[0].GetRow(); ++i )
606  selectedRows.Add( i );
607  }
608 
609  // Use the row having the grid cursor only if we have no candidate:
610  if( selectedRows.size() == 0 && m_cur_grid->GetGridCursorRow() >= 0 )
611  selectedRows.Add( m_cur_grid->GetGridCursorRow() );
612 
613  if( selectedRows.size() == 0 )
614  {
615  wxBell();
616  return;
617  }
618 
619  std::sort( selectedRows.begin(), selectedRows.end() );
620 
621  // Remove selected rows (note: a row can be stored more than once in list)
622  int last_row = -1;
623  for( int ii = selectedRows.GetCount()-1; ii >= 0; ii-- )
624  {
625  int row = selectedRows[ii];
626 
627  if( row != last_row )
628  {
629  last_row = row;
630  m_cur_grid->DeleteRows( row, 1 );
631  }
632  }
633 
634  m_cur_grid->SetGridCursor( std::min( curRow, m_cur_grid->GetNumberRows() - 1 ), curCol );
635 }
636 
637 
638 void PANEL_FP_LIB_TABLE::moveUpHandler( wxCommandEvent& event )
639 {
641  return;
642 
643  FP_LIB_TABLE_GRID* tbl = cur_model();
644  int curRow = m_cur_grid->GetGridCursorRow();
645 
646  // @todo: add multiple selection moves.
647  if( curRow >= 1 )
648  {
649  boost::ptr_vector< LIB_TABLE_ROW >::auto_type move_me =
650  tbl->rows.release( tbl->rows.begin() + curRow );
651 
652  --curRow;
653  tbl->rows.insert( tbl->rows.begin() + curRow, move_me.release() );
654 
655  if( tbl->GetView() )
656  {
657  // Update the wxGrid
658  wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, curRow, 0 );
659  tbl->GetView()->ProcessTableMessage( msg );
660  }
661 
662  m_cur_grid->MakeCellVisible( curRow, m_cur_grid->GetGridCursorCol() );
663  m_cur_grid->SetGridCursor( curRow, m_cur_grid->GetGridCursorCol() );
664  }
665 }
666 
667 
668 void PANEL_FP_LIB_TABLE::moveDownHandler( wxCommandEvent& event )
669 {
671  return;
672 
673  FP_LIB_TABLE_GRID* tbl = cur_model();
674  int curRow = m_cur_grid->GetGridCursorRow();
675 
676  // @todo: add multiple selection moves.
677  if( unsigned( curRow + 1 ) < tbl->rows.size() )
678  {
679  boost::ptr_vector< LIB_TABLE_ROW >::auto_type move_me =
680  tbl->rows.release( tbl->rows.begin() + curRow );
681 
682  ++curRow;
683  tbl->rows.insert( tbl->rows.begin() + curRow, move_me.release() );
684 
685  if( tbl->GetView() )
686  {
687  // Update the wxGrid
688  wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, curRow - 1, 0 );
689  tbl->GetView()->ProcessTableMessage( msg );
690  }
691 
692  m_cur_grid->MakeCellVisible( curRow, m_cur_grid->GetGridCursorCol() );
693  m_cur_grid->SetGridCursor( curRow, m_cur_grid->GetGridCursorCol() );
694  }
695 }
696 
697 
698 void PANEL_FP_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event )
699 {
701  return;
702 
703  std::map<int, supportedFileType>::const_iterator fileTypeIt;
704 
705  // We are bound both to the menu and button with this one handler
706  // So we must set the file type based on it
707  if( event.GetEventType() == wxEVT_BUTTON )
708  {
709  // Let's default to adding a kicad module for just the module
710  fileTypeIt = fileTypes().find( ID_PANEL_FPLIB_ADD_KICADMOD );
711  }
712  else
713  {
714  fileTypeIt = fileTypes().find( event.GetId() );
715  }
716 
717  if( fileTypeIt == fileTypes().end() )
718  {
719  wxLogWarning( "File type selection event received but could not find the file type in the table" );
720  return;
721  }
722 
723  supportedFileType fileType = fileTypeIt->second;
724 
725  if( m_lastBrowseDir.IsEmpty() )
727 
728  wxArrayString files;
729 
730  wxString title;
731 
732  title.Printf( _( "Select %s Library" ), fileType.m_Description );
733 
734  if( fileType.m_IsFile )
735  {
736  wxFileDialog dlg( this, title, m_lastBrowseDir, wxEmptyString,
737  fileType.m_FileFilter, wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE );
738 
739  int result = dlg.ShowModal();
740 
741  if( result == wxID_CANCEL )
742  return;
743 
744  dlg.GetPaths( files );
745 
746  m_lastBrowseDir = dlg.GetDirectory();
747  }
748  else
749  {
750  wxDirDialog dlg( nullptr, title, m_lastBrowseDir,
751  wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST );
752 
753  int result = dlg.ShowModal();
754 
755  if( result == wxID_CANCEL )
756  return;
757 
758  // is there a file extension configured to hunt out their containing folders?
759  if( fileType.m_FolderSearchExtension != "" )
760  {
761  wxDir rootDir( dlg.GetPath() );
762 
763  LIBRARY_TRAVERSER traverser( fileType.m_FolderSearchExtension, rootDir.GetName() );
764  rootDir.Traverse( traverser );
765 
766  traverser.GetPaths( files );
767 
768  if( traverser.HasDirectoryOpenFailures() )
769  {
770  wxArrayString failedDirs;
771  traverser.GetPaths( failedDirs );
772  wxString detailedMsg = _( "The following directories could not be opened: \n" );
773 
774  for( auto& path : failedDirs )
775  detailedMsg << path << "\n";
776 
777  DisplayErrorMessage( this, _( "Failed to open directories to look for libraries" ),
778  detailedMsg );
779  }
780  }
781  else
782  {
783  files.Add( dlg.GetPath() );
784  }
785 
786  m_lastBrowseDir = dlg.GetPath();
787  }
788 
789  // Drop the last directory if the path is a .pretty folder
791  m_lastBrowseDir = m_lastBrowseDir.BeforeLast( wxFileName::GetPathSeparator() );
792 
793  const ENV_VAR_MAP& envVars = Pgm().GetLocalEnvVariables();
794  bool addDuplicates = false;
795  bool applyToAll = false;
796  wxString warning = _( "Warning: Duplicate Nickname" );
797  wxString msg = _( "A library nicknamed \"%s\" already exists." );
798  wxString detailedMsg = _( "Please change the library nickname after adding this library." );
799 
800  for( const auto& filePath : files )
801  {
802  wxFileName fn( filePath );
803  wxString nickname = LIB_ID::FixIllegalChars( fn.GetName(), LIB_ID::ID_PCB );
804  bool doAdd = true;
805 
806  if( cur_model()->ContainsNickname( nickname ) )
807  {
808  if( !applyToAll )
809  {
810  // The cancel button adds the library to the table anyway
811  addDuplicates = ( OKOrCancelDialog( this, warning, wxString::Format( msg, nickname ),
812  detailedMsg, _( "Skip" ), _( "Add Anyway" ), &applyToAll ) == wxID_CANCEL );
813  }
814 
815  doAdd = addDuplicates;
816  }
817 
818  if( doAdd && m_cur_grid->AppendRows( 1 ) )
819  {
820  int last_row = m_cur_grid->GetNumberRows() - 1;
821 
822  m_cur_grid->SetCellValue( last_row, COL_NICKNAME, nickname );
823 
824  m_cur_grid->SetCellValue( last_row, COL_TYPE, IO_MGR::ShowType( fileType.m_Plugin ) );
825 
826  // try to use path normalized to an environmental variable or project path
827  wxString path = NormalizePath( filePath, &envVars, m_projectBasePath );
828 
829  // Do not use the project path in the global library table. This will almost
830  // assuredly be wrong for a different project.
831  if( path.IsEmpty() || ( m_pageNdx == 0 && path.Contains( "${KIPRJMOD}" ) ) )
832  path = fn.GetFullPath();
833 
834  m_cur_grid->SetCellValue( last_row, COL_URI, path );
835  }
836  }
837 
838  if( !files.IsEmpty() )
839  {
840  int new_row = m_cur_grid->GetNumberRows() - 1;
841  m_cur_grid->MakeCellVisible( new_row, m_cur_grid->GetGridCursorCol() );
842  m_cur_grid->SetGridCursor( new_row, m_cur_grid->GetGridCursorCol() );
843  }
844 }
845 
846 
848 {
849  // Account for scroll bars
850  aWidth -= ( m_path_subs_grid->GetSize().x - m_path_subs_grid->GetClientSize().x );
851 
852  m_path_subs_grid->AutoSizeColumn( 0 );
853  m_path_subs_grid->SetColSize( 1, aWidth - m_path_subs_grid->GetColSize( 0 ) );
854 }
855 
856 
857 void PANEL_FP_LIB_TABLE::onSizeGrid( wxSizeEvent& event )
858 {
859  adjustPathSubsGridColumns( event.GetSize().GetX() );
860 
861  event.Skip();
862 }
863 
864 
866 {
868  return false;
869 
870  if( verifyTables() )
871  {
872  if( *global_model() != *m_global )
873  {
875 
876  m_global->Clear();
877  m_global->rows.transfer( m_global->rows.end(), global_model()->rows.begin(),
878  global_model()->rows.end(), global_model()->rows );
879  m_global->reindex();
880  }
881 
882  if( *project_model() != *m_project )
883  {
885 
886  m_project->Clear();
887  m_project->rows.transfer( m_project->rows.end(), project_model()->rows.begin(),
888  project_model()->rows.end(), project_model()->rows );
889  m_project->reindex();
890  }
891 
892  return true;
893  }
894 
895  return false;
896 }
897 
898 
902 {
903  wxRegEx re( ".*?(\\$\\{(.+?)\\})|(\\$\\((.+?)\\)).*?", wxRE_ADVANCED );
904  wxASSERT( re.IsValid() ); // wxRE_ADVANCED is required.
905 
906  std::set< wxString > unique;
907 
908  // clear the table
909  m_path_subs_grid->DeleteRows( 0, m_path_subs_grid->GetNumberRows() );
910 
911  for( FP_LIB_TABLE_GRID* tbl : { global_model(), project_model() } )
912  {
913  for( int row = 0; row < tbl->GetNumberRows(); ++row )
914  {
915  wxString uri = tbl->GetValue( row, COL_URI );
916 
917  while( re.Matches( uri ) )
918  {
919  wxString envvar = re.GetMatch( uri, 2 );
920 
921  // if not ${...} form then must be $(...)
922  if( envvar.IsEmpty() )
923  envvar = re.GetMatch( uri, 4 );
924 
925  // ignore duplicates
926  unique.insert( envvar );
927 
928  // delete the last match and search again
929  uri.Replace( re.GetMatch( uri, 0 ), wxEmptyString );
930  }
931  }
932  }
933 
934  // Make sure this special environment variable shows up even if it was
935  // not used yet. It is automatically set by KiCad to the directory holding
936  // the current project.
937  unique.insert( PROJECT_VAR_NAME );
938  unique.insert( FP_LIB_TABLE::GlobalPathEnvVariableName() );
939  // This special environment variable is used to locate 3d shapes
940  unique.insert( KISYS3DMOD );
941 
942  for( const wxString& evName : unique )
943  {
944  int row = m_path_subs_grid->GetNumberRows();
945  m_path_subs_grid->AppendRows( 1 );
946 
947  m_path_subs_grid->SetCellValue( row, 0, wxT( "${" ) + evName + wxT( "}" ) );
948  m_path_subs_grid->SetCellEditor( row, 0, new GRID_CELL_READONLY_TEXT_EDITOR() );
949 
950  wxString evValue;
951  wxGetEnv( evName, &evValue );
952  m_path_subs_grid->SetCellValue( row, 1, evValue );
953  m_path_subs_grid->SetCellEditor( row, 1, new GRID_CELL_READONLY_TEXT_EDITOR() );
954  }
955 
956  // No combobox editors here, but it looks better if its consistent with the other
957  // grids in the dialog.
958  m_path_subs_grid->SetDefaultRowSize( m_path_subs_grid->GetDefaultRowSize() + 2 );
959 
960  adjustPathSubsGridColumns( m_path_subs_grid->GetRect().GetWidth() );
961 }
962 
963 //-----</event handlers>---------------------------------
964 
965 
966 
968 
970 
971 
972 void InvokePcbLibTableEditor( KIWAY* aKiway, wxWindow* aCaller )
973 {
974  FP_LIB_TABLE* globalTable = &GFootprintTable;
975  wxString globalTablePath = FP_LIB_TABLE::GetGlobalTableFileName();
976  FP_LIB_TABLE* projectTable = aKiway->Prj().PcbFootprintLibs();
977  wxString projectTablePath = aKiway->Prj().FootprintLibTblName();
978  wxString msg;
979 
980  DIALOG_EDIT_LIBRARY_TABLES dlg( aCaller, _( "Footprint Libraries" ) );
981  dlg.SetKiway( &dlg, aKiway );
982 
983  dlg.InstallPanel( new PANEL_FP_LIB_TABLE( &dlg, globalTable, globalTablePath,
984  projectTable, projectTablePath,
985  aKiway->Prj().GetProjectPath() ) );
986 
987  if( dlg.ShowModal() == wxID_CANCEL )
988  return;
989 
990  if( dlg.m_GlobalTableChanged )
991  {
992  try
993  {
994  globalTable->Save( globalTablePath );
995  }
996  catch( const IO_ERROR& ioe )
997  {
998  msg.Printf( _( "Error saving global library table:\n\n%s" ), ioe.What() );
999  wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
1000  }
1001  }
1002 
1003  if( dlg.m_ProjectTableChanged )
1004  {
1005  try
1006  {
1007  projectTable->Save( projectTablePath );
1008  }
1009  catch( const IO_ERROR& ioe )
1010  {
1011  msg.Printf( _( "Error saving project-specific library table:\n\n%s" ), ioe.What() );
1012  wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
1013  }
1014  }
1015 
1016  auto editor = (FOOTPRINT_EDIT_FRAME*) aKiway->Player( FRAME_FOOTPRINT_EDITOR, false );
1017 
1018  if( editor )
1019  editor->SyncLibraryTree( true );
1020 
1021  auto viewer = (FOOTPRINT_VIEWER_FRAME*) aKiway->Player( FRAME_FOOTPRINT_VIEWER, false );
1022 
1023  if( viewer )
1024  viewer->ReCreateLibraryList();
1025 }
void DisplayError(wxWindow *aParent, const wxString &aText, int aDisplayTime)
Display an error or warning message box with aMessage.
Definition: confirm.cpp:239
FP_GRID_TRICKS(DIALOG_EDIT_LIBRARY_TABLES *aParent, WX_GRID *aGrid)
const BITMAP_OPAQUE trash_xpm[1]
Definition: trash.cpp:46
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
KIWAY Kiway & Pgm(), KFCTL_STANDALONE
The global Program "get" accessor.
Definition: single_top.cpp:103
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:252
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:171
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:51
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
static wxString m_lastBrowseDir
#define KISYS3DMOD
A variable name whose value holds the path of 3D shape files.
Definition: eda_3d_viewer.h:44
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:57
static const wxString ShowType(PCB_FILE_T aFileType)
Function ShowType returns a brief name for a plugin, given aFileType enum.
Definition: io_mgr.cpp:77
virtual const wxString GetType() const =0
Return the type of library represented by this row.
int GetNumberRows() override
wxString LegacyFootprintLibPathWildcard()
VTBL_ENTRY const wxString GetProjectPath() const
Function GetProjectPath returns the full path of the project.
Definition: project.cpp:102
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:60
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:58
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:48
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:80
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:341
virtual const wxString What() const
A composite of Problem() and Where()
Definition: exceptions.cpp:33
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:25
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
bool CommitPendingChanges(bool aQuietMode=false)
Close any open cell edit controls.
Definition: wx_grid.cpp:172
bool AppendRows(size_t aNumRows=1) override
void optionsEditor(int aRow)
FormatType fileType(const char *aFileName)
Definition: loadmodel.cpp:269
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:214
const BITMAP_OPAQUE small_up_xpm[1]
Definition: small_up.cpp:26
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:427
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:205
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
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:254
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:120
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