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 <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 
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 
391  auto setupGrid =
392  [&]( WX_GRID* aGrid )
393  {
394  // Give a bit more room for wxChoice editors
395  aGrid->SetDefaultRowSize( aGrid->GetDefaultRowSize() + 4 );
396 
397  // add Cut, Copy, and Paste to wxGrids
398  aGrid->PushEventHandler( new FP_GRID_TRICKS( m_parent, aGrid ) );
399 
400  aGrid->SetSelectionMode( wxGrid::wxGridSelectRows );
401  aGrid->AutoSizeColumns( false );
402 
403  wxGridCellAttr* attr;
404 
405  attr = new wxGridCellAttr;
406  attr->SetEditor( new GRID_CELL_PATH_EDITOR( m_parent, &m_lastBrowseDir,
407  wxEmptyString ) );
408  aGrid->SetColAttr( COL_URI, attr );
409 
410  attr = new wxGridCellAttr;
411  attr->SetEditor( new wxGridCellChoiceEditor( choices ) );
412  aGrid->SetColAttr( COL_TYPE, attr );
413 
414  attr = new wxGridCellAttr;
415  attr->SetRenderer( new wxGridCellBoolRenderer() );
416  attr->SetReadOnly(); // not really; we delegate interactivity to GRID_TRICKS
417  aGrid->SetColAttr( COL_ENABLED, attr );
418 
419  // all but COL_OPTIONS, which is edited with Option Editor anyways.
420  aGrid->AutoSizeColumn( COL_NICKNAME, false );
421  aGrid->AutoSizeColumn( COL_TYPE, false );
422  aGrid->AutoSizeColumn( COL_URI, false );
423  aGrid->AutoSizeColumn( COL_DESCR, false );
424 
425  // would set this to width of title, if it was easily known.
426  aGrid->SetColSize( COL_OPTIONS, 80 );
427 
428  // Gives a selection to each grid, mainly for delete button. wxGrid's wake up with
429  // a currentCell which is sometimes not highlighted.
430  if( aGrid->GetNumberRows() > 0 )
431  aGrid->SelectRow( 0 );
432  };
433 
434  setupGrid( m_global_grid );
435 
437 
438  if( aProject )
439  {
440  m_PrjTableFilename->SetLabel( aProjectTblPath );
441  m_project_grid->SetTable( new FP_LIB_TABLE_GRID( *aProject ), true );
442  setupGrid( m_project_grid );
443  }
444  else
445  {
446  m_pageNdx = 0;
447  m_auinotebook->DeletePage( 1 );
448  m_project_grid = nullptr;
449  }
450 
451  m_path_subs_grid->SetColLabelValue( 0, _( "Name" ) );
452  m_path_subs_grid->SetColLabelValue( 1, _( "Value" ) );
453 
454  // select the last selected page
455  m_auinotebook->SetSelection( m_pageNdx );
457 
458  // for ALT+A handling, we want the initial focus to be on the first selected grid.
460 
461  // Configure button logos
462  m_append_button->SetBitmap( KiBitmap( small_plus_xpm ) );
463  m_delete_button->SetBitmap( KiBitmap( trash_xpm ) );
464  m_move_up_button->SetBitmap( KiBitmap( small_up_xpm ) );
465  m_move_down_button->SetBitmap( KiBitmap( small_down_xpm ) );
467 
468  // For aesthetic reasons, we must set the size of m_browseButton to match
469  // the other bitmaps manually (for instance m_append_button)
470  Layout(); // Needed at least on MSW to compute the actual buttons sizes,
471  // after initializing their bitmaps
472  wxSize buttonSize = m_append_button->GetSize();
473 
475  m_browseButton->SetMinSize( buttonSize );
476 
477  // Populate the browse library options
478  wxMenu* browseMenu = m_browseButton->GetSplitButtonMenu();
479 
480  for( auto& fileType : fileTypes() )
481  {
482  browseMenu->Append( fileType.first, fileType.second.m_Description );
483 
484  browseMenu->Bind( wxEVT_COMMAND_MENU_SELECTED, &PANEL_FP_LIB_TABLE::browseLibrariesHandler,
485  this, fileType.first );
486  }
487 
488  Layout();
489 
490  // This is the button only press for the browse button instead of the menu
491  m_browseButton->Bind( wxEVT_BUTTON, &PANEL_FP_LIB_TABLE::browseLibrariesHandler, this );
492 }
493 
494 
496 {
497  // When the dialog is closed it will hide the current notebook page first, which will
498  // in turn select the other one. We then end up saving its index as the "current page".
499  // So flip them back again:
500  m_pageNdx = m_pageNdx == 1 ? 0 : 1;
501 
502  // Delete the GRID_TRICKS.
503  // Any additional event handlers should be popped before the window is deleted.
504  m_global_grid->PopEventHandler( true );
505 
506  if( m_project_grid )
507  m_project_grid->PopEventHandler( true );
508 
509  m_path_subs_grid->PopEventHandler( true );
510 }
511 
512 
514 {
515  for( FP_LIB_TABLE_GRID* model : { global_model(), project_model() } )
516  {
517  if( !model )
518  continue;
519 
520  for( int r = 0; r < model->GetNumberRows(); )
521  {
522  wxString nick = model->GetValue( r, COL_NICKNAME ).Trim( false ).Trim();
523  wxString uri = model->GetValue( r, COL_URI ).Trim( false ).Trim();
524  unsigned illegalCh = 0;
525 
526  if( !nick || !uri )
527  {
528  // Delete the "empty" row, where empty means missing nick or uri.
529  // This also updates the UI which could be slow, but there should only be a few
530  // rows to delete, unless the user fell asleep on the Add Row
531  // button.
532  model->DeleteRows( r, 1 );
533  }
534  else if( ( illegalCh = LIB_ID::FindIllegalLibNicknameChar( nick, LIB_ID::ID_PCB ) ) )
535  {
536  wxString msg = wxString::Format( _( "Illegal character '%c' in Nickname: \"%s\"" ),
537  illegalCh,
538  nick );
539 
540  // show the tabbed panel holding the grid we have flunked:
541  if( model != cur_model() )
542  m_auinotebook->SetSelection( model == global_model() ? 0 : 1 );
543 
544  m_cur_grid->MakeCellVisible( r, 0 );
545  m_cur_grid->SetGridCursor( r, 1 );
546 
547  wxMessageDialog errdlg( this, msg, _( "No Colon in Nicknames" ) );
548  errdlg.ShowModal();
549  return false;
550  }
551  else
552  {
553  // set the trimmed values back into the table so they get saved to disk.
554  model->SetValue( r, COL_NICKNAME, nick );
555  model->SetValue( r, COL_URI, uri );
556  ++r; // this row was OK.
557  }
558  }
559  }
560 
561  // check for duplicate nickNames, separately in each table.
562  for( FP_LIB_TABLE_GRID* model : { global_model(), project_model() } )
563  {
564  if( !model )
565  continue;
566 
567  for( int r1 = 0; r1 < model->GetNumberRows() - 1; ++r1 )
568  {
569  wxString nick1 = model->GetValue( r1, COL_NICKNAME );
570 
571  for( int r2 = r1 + 1; r2 < model->GetNumberRows(); ++r2 )
572  {
573  wxString nick2 = model->GetValue( r2, COL_NICKNAME );
574 
575  if( nick1 == nick2 )
576  {
577  wxString msg = wxString::Format( _( "Duplicate Nicknames \"%s\"." ), nick1 );
578 
579  // show the tabbed panel holding the grid we have flunked:
580  if( model != cur_model() )
581  m_auinotebook->SetSelection( model == global_model() ? 0 : 1 );
582 
583  // go to the lower of the two rows, it is technically the duplicate:
584  m_cur_grid->MakeCellVisible( r2, 0 );
585  m_cur_grid->SetGridCursor( r2, 1 );
586 
587  wxMessageDialog errdlg( this, msg, _( "Please Delete or Modify One" ) );
588  errdlg.ShowModal();
589  return false;
590  }
591  }
592  }
593  }
594 
595  return true;
596 }
597 
598 
599 //-----<event handlers>----------------------------------
600 
601 void PANEL_FP_LIB_TABLE::pageChangedHandler( wxAuiNotebookEvent& event )
602 {
603  m_pageNdx = (unsigned) std::max( 0, m_auinotebook->GetSelection() );
605 }
606 
607 
608 void PANEL_FP_LIB_TABLE::appendRowHandler( wxCommandEvent& event )
609 {
611  return;
612 
613  if( m_cur_grid->AppendRows( 1 ) )
614  {
615  int last_row = m_cur_grid->GetNumberRows() - 1;
616 
617  // wx documentation is wrong, SetGridCursor does not make visible.
618  m_cur_grid->MakeCellVisible( last_row, 0 );
619  m_cur_grid->SetGridCursor( last_row, 1 );
620  m_cur_grid->EnableCellEditControl( true );
621  m_cur_grid->ShowCellEditControl();
622  }
623 }
624 
625 
626 void PANEL_FP_LIB_TABLE::deleteRowHandler( wxCommandEvent& event )
627 {
629  return;
630 
631  int curRow = m_cur_grid->GetGridCursorRow();
632  int curCol = m_cur_grid->GetGridCursorCol();
633 
634  // In a wxGrid, collect rows that have a selected cell, or are selected
635  // It is not so easy: it depends on the way the selection was made.
636  // Here, we collect rows selected by clicking on a row label, and rows that contain any
637  // previously-selected cells.
638  // If no candidate, just delete the row with the grid cursor.
639  wxArrayInt selectedRows = m_cur_grid->GetSelectedRows();
640  wxGridCellCoordsArray cells = m_cur_grid->GetSelectedCells();
641  wxGridCellCoordsArray blockTopLeft = m_cur_grid->GetSelectionBlockTopLeft();
642  wxGridCellCoordsArray blockBotRight = m_cur_grid->GetSelectionBlockBottomRight();
643 
644  // Add all row having cell selected to list:
645  for( unsigned ii = 0; ii < cells.GetCount(); ii++ )
646  selectedRows.Add( cells[ii].GetRow() );
647 
648  // Handle block selection
649  if( !blockTopLeft.IsEmpty() && !blockBotRight.IsEmpty() )
650  {
651  for( int i = blockTopLeft[0].GetRow(); i <= blockBotRight[0].GetRow(); ++i )
652  selectedRows.Add( i );
653  }
654 
655  // Use the row having the grid cursor only if we have no candidate:
656  if( selectedRows.size() == 0 && m_cur_grid->GetGridCursorRow() >= 0 )
657  selectedRows.Add( m_cur_grid->GetGridCursorRow() );
658 
659  if( selectedRows.size() == 0 )
660  {
661  wxBell();
662  return;
663  }
664 
665  std::sort( selectedRows.begin(), selectedRows.end() );
666 
667  // Remove selected rows (note: a row can be stored more than once in list)
668  int last_row = -1;
669 
670  for( int ii = selectedRows.GetCount()-1; ii >= 0; ii-- )
671  {
672  int row = selectedRows[ii];
673 
674  if( row != last_row )
675  {
676  last_row = row;
677  m_cur_grid->DeleteRows( row, 1 );
678  }
679  }
680 
681  if( m_cur_grid->GetNumberRows() > 0 && curRow >= 0 )
682  m_cur_grid->SetGridCursor( std::min( curRow, m_cur_grid->GetNumberRows() - 1 ), curCol );
683 }
684 
685 
686 void PANEL_FP_LIB_TABLE::moveUpHandler( wxCommandEvent& event )
687 {
689  return;
690 
691  FP_LIB_TABLE_GRID* tbl = cur_model();
692  int curRow = m_cur_grid->GetGridCursorRow();
693 
694  // @todo: add multiple selection moves.
695  if( curRow >= 1 )
696  {
697  boost::ptr_vector< LIB_TABLE_ROW >::auto_type move_me =
698  tbl->rows.release( tbl->rows.begin() + curRow );
699 
700  --curRow;
701  tbl->rows.insert( tbl->rows.begin() + curRow, move_me.release() );
702 
703  if( tbl->GetView() )
704  {
705  // Update the wxGrid
706  wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, curRow, 0 );
707  tbl->GetView()->ProcessTableMessage( msg );
708  }
709 
710  m_cur_grid->MakeCellVisible( curRow, m_cur_grid->GetGridCursorCol() );
711  m_cur_grid->SetGridCursor( curRow, m_cur_grid->GetGridCursorCol() );
712  }
713 }
714 
715 
716 void PANEL_FP_LIB_TABLE::moveDownHandler( wxCommandEvent& event )
717 {
719  return;
720 
721  FP_LIB_TABLE_GRID* tbl = cur_model();
722  int curRow = m_cur_grid->GetGridCursorRow();
723 
724  // @todo: add multiple selection moves.
725  if( unsigned( curRow + 1 ) < tbl->rows.size() )
726  {
727  boost::ptr_vector< LIB_TABLE_ROW >::auto_type move_me =
728  tbl->rows.release( tbl->rows.begin() + curRow );
729 
730  ++curRow;
731  tbl->rows.insert( tbl->rows.begin() + curRow, move_me.release() );
732 
733  if( tbl->GetView() )
734  {
735  // Update the wxGrid
736  wxGridTableMessage msg( tbl, wxGRIDTABLE_NOTIFY_ROWS_INSERTED, curRow - 1, 0 );
737  tbl->GetView()->ProcessTableMessage( msg );
738  }
739 
740  m_cur_grid->MakeCellVisible( curRow, m_cur_grid->GetGridCursorCol() );
741  m_cur_grid->SetGridCursor( curRow, m_cur_grid->GetGridCursorCol() );
742  }
743 }
744 
745 
746 void PANEL_FP_LIB_TABLE::browseLibrariesHandler( wxCommandEvent& event )
747 {
749  return;
750 
751  std::map<int, supportedFileType>::const_iterator fileTypeIt;
752 
753  // We are bound both to the menu and button with this one handler
754  // So we must set the file type based on it
755  if( event.GetEventType() == wxEVT_BUTTON )
756  {
757  // Let's default to adding a kicad module for just the module
758  fileTypeIt = fileTypes().find( ID_PANEL_FPLIB_ADD_KICADMOD );
759  }
760  else
761  {
762  fileTypeIt = fileTypes().find( event.GetId() );
763  }
764 
765  if( fileTypeIt == fileTypes().end() )
766  {
767  wxLogWarning( "File type selection event received but could not find the file type in the table" );
768  return;
769  }
770 
771  supportedFileType fileType = fileTypeIt->second;
772 
773  if( m_lastBrowseDir.IsEmpty() )
775 
776  wxArrayString files;
777 
778  wxString title;
779 
780  title.Printf( _( "Select %s Library" ), fileType.m_Description );
781 
782  if( fileType.m_IsFile )
783  {
784  wxFileDialog dlg( this, title, m_lastBrowseDir, wxEmptyString,
785  fileType.m_FileFilter, wxFD_OPEN | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE );
786 
787  int result = dlg.ShowModal();
788 
789  if( result == wxID_CANCEL )
790  return;
791 
792  dlg.GetPaths( files );
793 
794  m_lastBrowseDir = dlg.GetDirectory();
795  }
796  else
797  {
798  wxDirDialog dlg( nullptr, title, m_lastBrowseDir,
799  wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST );
800 
801  int result = dlg.ShowModal();
802 
803  if( result == wxID_CANCEL )
804  return;
805 
806  // is there a file extension configured to hunt out their containing folders?
807  if( fileType.m_FolderSearchExtension != "" )
808  {
809  wxDir rootDir( dlg.GetPath() );
810 
811  LIBRARY_TRAVERSER traverser( fileType.m_FolderSearchExtension, rootDir.GetName() );
812  rootDir.Traverse( traverser );
813 
814  traverser.GetPaths( files );
815 
816  if( traverser.HasDirectoryOpenFailures() )
817  {
818  wxArrayString failedDirs;
819  traverser.GetPaths( failedDirs );
820  wxString detailedMsg = _( "The following directories could not be opened: \n" );
821 
822  for( auto& path : failedDirs )
823  detailedMsg << path << "\n";
824 
825  DisplayErrorMessage( this, _( "Failed to open directories to look for libraries" ),
826  detailedMsg );
827  }
828  }
829  else
830  {
831  files.Add( dlg.GetPath() );
832  }
833 
834  m_lastBrowseDir = dlg.GetPath();
835  }
836 
837  // Drop the last directory if the path is a .pretty folder
839  m_lastBrowseDir = m_lastBrowseDir.BeforeLast( wxFileName::GetPathSeparator() );
840 
841  const ENV_VAR_MAP& envVars = Pgm().GetLocalEnvVariables();
842  bool addDuplicates = false;
843  bool applyToAll = false;
844  wxString warning = _( "Warning: Duplicate Nickname" );
845  wxString msg = _( "A library nicknamed \"%s\" already exists." );
846  wxString detailedMsg = _( "Please change the library nickname after adding this library." );
847 
848  for( const auto& filePath : files )
849  {
850  wxFileName fn( filePath );
851  wxString nickname = LIB_ID::FixIllegalChars( fn.GetName(), LIB_ID::ID_PCB );
852  bool doAdd = true;
853 
854  if( cur_model()->ContainsNickname( nickname ) )
855  {
856  if( !applyToAll )
857  {
858  // The cancel button adds the library to the table anyway
859  addDuplicates = ( OKOrCancelDialog( this, warning, wxString::Format( msg, nickname ),
860  detailedMsg, _( "Skip" ), _( "Add Anyway" ), &applyToAll ) == wxID_CANCEL );
861  }
862 
863  doAdd = addDuplicates;
864  }
865 
866  if( doAdd && m_cur_grid->AppendRows( 1 ) )
867  {
868  int last_row = m_cur_grid->GetNumberRows() - 1;
869 
870  m_cur_grid->SetCellValue( last_row, COL_NICKNAME, nickname );
871 
872  m_cur_grid->SetCellValue( last_row, COL_TYPE, IO_MGR::ShowType( fileType.m_Plugin ) );
873 
874  // try to use path normalized to an environmental variable or project path
875  wxString path = NormalizePath( filePath, &envVars, m_projectBasePath );
876 
877  // Do not use the project path in the global library table. This will almost
878  // assuredly be wrong for a different project.
879  if( path.IsEmpty() || ( m_pageNdx == 0 && path.Contains( "${KIPRJMOD}" ) ) )
880  path = fn.GetFullPath();
881 
882  m_cur_grid->SetCellValue( last_row, COL_URI, path );
883  }
884  }
885 
886  if( !files.IsEmpty() )
887  {
888  int new_row = m_cur_grid->GetNumberRows() - 1;
889  m_cur_grid->MakeCellVisible( new_row, m_cur_grid->GetGridCursorCol() );
890  m_cur_grid->SetGridCursor( new_row, m_cur_grid->GetGridCursorCol() );
891  }
892 }
893 
894 
896 {
897  // Account for scroll bars
898  aWidth -= ( m_path_subs_grid->GetSize().x - m_path_subs_grid->GetClientSize().x );
899 
900  m_path_subs_grid->AutoSizeColumn( 0 );
901  m_path_subs_grid->SetColSize( 1, aWidth - m_path_subs_grid->GetColSize( 0 ) );
902 }
903 
904 
905 void PANEL_FP_LIB_TABLE::onSizeGrid( wxSizeEvent& event )
906 {
907  adjustPathSubsGridColumns( event.GetSize().GetX() );
908 
909  event.Skip();
910 }
911 
912 
914 {
916  return false;
917 
918  if( verifyTables() )
919  {
920  if( *global_model() != *m_global )
921  {
923 
924  m_global->Clear();
925  m_global->rows.transfer( m_global->rows.end(), global_model()->rows.begin(),
926  global_model()->rows.end(), global_model()->rows );
927  m_global->reindex();
928  }
929 
930  if( project_model() && *project_model() != *m_project )
931  {
933 
934  m_project->Clear();
935  m_project->rows.transfer( m_project->rows.end(), project_model()->rows.begin(),
936  project_model()->rows.end(), project_model()->rows );
937  m_project->reindex();
938  }
939 
940  return true;
941  }
942 
943  return false;
944 }
945 
946 
950 {
951  wxRegEx re( ".*?(\\$\\{(.+?)\\})|(\\$\\((.+?)\\)).*?", wxRE_ADVANCED );
952  wxASSERT( re.IsValid() ); // wxRE_ADVANCED is required.
953 
954  std::set< wxString > unique;
955 
956  // clear the table
957  m_path_subs_grid->DeleteRows( 0, m_path_subs_grid->GetNumberRows() );
958 
959  for( FP_LIB_TABLE_GRID* tbl : { global_model(), project_model() } )
960  {
961  if( !tbl )
962  continue;
963 
964  for( int row = 0; row < tbl->GetNumberRows(); ++row )
965  {
966  wxString uri = tbl->GetValue( row, COL_URI );
967 
968  while( re.Matches( uri ) )
969  {
970  wxString envvar = re.GetMatch( uri, 2 );
971 
972  // if not ${...} form then must be $(...)
973  if( envvar.IsEmpty() )
974  envvar = re.GetMatch( uri, 4 );
975 
976  // ignore duplicates
977  unique.insert( envvar );
978 
979  // delete the last match and search again
980  uri.Replace( re.GetMatch( uri, 0 ), wxEmptyString );
981  }
982  }
983  }
984 
985  // Make sure this special environment variable shows up even if it was
986  // not used yet. It is automatically set by KiCad to the directory holding
987  // the current project.
988  unique.insert( PROJECT_VAR_NAME );
989  unique.insert( FP_LIB_TABLE::GlobalPathEnvVariableName() );
990  // This special environment variable is used to locate 3d shapes
991  unique.insert( KISYS3DMOD );
992 
993  for( const wxString& evName : unique )
994  {
995  int row = m_path_subs_grid->GetNumberRows();
996  m_path_subs_grid->AppendRows( 1 );
997 
998  m_path_subs_grid->SetCellValue( row, 0, wxT( "${" ) + evName + wxT( "}" ) );
999  m_path_subs_grid->SetCellEditor( row, 0, new GRID_CELL_READONLY_TEXT_EDITOR() );
1000 
1001  wxString evValue;
1002  wxGetEnv( evName, &evValue );
1003  m_path_subs_grid->SetCellValue( row, 1, evValue );
1004  m_path_subs_grid->SetCellEditor( row, 1, new GRID_CELL_READONLY_TEXT_EDITOR() );
1005  }
1006 
1007  // No combobox editors here, but it looks better if its consistent with the other
1008  // grids in the dialog.
1009  m_path_subs_grid->SetDefaultRowSize( m_path_subs_grid->GetDefaultRowSize() + 2 );
1010 
1011  adjustPathSubsGridColumns( m_path_subs_grid->GetRect().GetWidth() );
1012 }
1013 
1014 //-----</event handlers>---------------------------------
1015 
1016 
1017 
1019 
1021 
1022 
1023 void InvokePcbLibTableEditor( KIWAY* aKiway, wxWindow* aCaller )
1024 {
1025  FP_LIB_TABLE* globalTable = &GFootprintTable;
1026  wxString globalTablePath = FP_LIB_TABLE::GetGlobalTableFileName();
1027  FP_LIB_TABLE* projectTable = aKiway->Prj().PcbFootprintLibs();
1028  wxString projectTablePath = aKiway->Prj().FootprintLibTblName();
1029  wxString msg;
1030 
1031  DIALOG_EDIT_LIBRARY_TABLES dlg( aCaller, _( "Footprint Libraries" ) );
1032  dlg.SetKiway( &dlg, aKiway );
1033 
1034  if( aKiway->Prj().IsNullProject() )
1035  projectTable = nullptr;
1036 
1037  dlg.InstallPanel( new PANEL_FP_LIB_TABLE( &dlg, globalTable, globalTablePath,
1038  projectTable, projectTablePath,
1039  aKiway->Prj().GetProjectPath() ) );
1040 
1041  if( dlg.ShowModal() == wxID_CANCEL )
1042  return;
1043 
1044  if( dlg.m_GlobalTableChanged )
1045  {
1046  try
1047  {
1048  globalTable->Save( globalTablePath );
1049  }
1050  catch( const IO_ERROR& ioe )
1051  {
1052  msg.Printf( _( "Error saving global library table:\n\n%s" ), ioe.What() );
1053  wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
1054  }
1055  }
1056 
1057  if( projectTable && dlg.m_ProjectTableChanged )
1058  {
1059  try
1060  {
1061  projectTable->Save( projectTablePath );
1062  }
1063  catch( const IO_ERROR& ioe )
1064  {
1065  msg.Printf( _( "Error saving project-specific library table:\n\n%s" ), ioe.What() );
1066  wxMessageBox( msg, _( "File Save Error" ), wxOK | wxICON_ERROR );
1067  }
1068  }
1069 
1070  auto editor = (FOOTPRINT_EDIT_FRAME*) aKiway->Player( FRAME_FOOTPRINT_EDITOR, false );
1071 
1072  if( editor )
1073  editor->SyncLibraryTree( true );
1074 
1075  auto viewer = (FOOTPRINT_VIEWER_FRAME*) aKiway->Player( FRAME_FOOTPRINT_VIEWER, false );
1076 
1077  if( viewer )
1078  viewer->ReCreateLibraryList();
1079 }
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:104
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:172
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: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:59
static const wxString ShowType(PCB_FILE_T aFileType)
Function ShowType returns a brief name for a plugin, given aFileType enum.
Definition: io_mgr.cpp:80
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:123
void SetInitialFocus(wxWindow *aWindow)
Sets the window (usually a wxTextCtrl) that should be focused when the dialog is shown.
Definition: dialog_shim.h:114
Geda PCB file formats.
Definition: io_mgr.h:63
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)
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:342
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: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:174
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
VTBL_ENTRY bool IsNullProject() const
Checks if this project is a null project (i.e.
Definition: project.cpp:135
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:291
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 ...
#define TO_UTF8(wxstring)
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:147
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